【问题标题】:Why does my variadic template instantiation not work?为什么我的可变参数模板实例化不起作用?
【发布时间】:2021-06-13 19:54:02
【问题描述】:

在长时间的中断之后,我正在重新审视 C++,我想使用模板来设计 the known "map" function -- 将函数应用于集合的每个元素的模板。

不管我的map 没有返回任何东西(这里是非因素),如果传递给“map”的函数不需要接受额外的参数,我已经设法实现了我想要的:

#include <iostream>

template <typename C, void fn(const typename C::value_type &)> void map(const C & c) {
    for(auto i : c) {
        fn(i);
    }
}

struct some_container_type { /// Just some hastily put together iterable structure type
    typedef int value_type;
    value_type * a;
    int n;
    some_container_type(value_type * a, int n): a(a), n(n) { }
    value_type * begin() const {
        return a;
    }
    value_type * end() const {
        return a + n; 
    }
};

void some_fn(const int & e) { /// A function used for testing the "map" function
    std::cout << "`fn` called for " << e << std::endl;
}

int main() {
    int a[] = { 5, 7, 12 };
    const some_container_type sc(a, std::size(a));
    map<some_container_type, some_fn>(sc);
}

但是,我希望map 接受额外的参数来调用fn。我试图编译程序的修改变体(容器类型定义未更改):

template <typename C, typename ... T, void fn(const typename C::value_type &, T ...)> void map(const C & c, T ... args) {
    for(auto i : c) {
        fn(i, args...);
    }
}

void some_fn(const int & e, int a, float b, char c) {
    std::cout << "`fn` called for " << e << std::endl;
}

int main() {
    int a[] = { 5, 7, 12 };
    const some_container_type sc(a, std::size(a));
    map<some_container_type, int, float, char, some_fn>(sc, 1, 2.0f, '3');
}

gcc -std=c++20 拒绝编译包含上述变体的修改程序,中止:

<source>: In function 'int main()':
<source>:29:56: error: no matching function for call to 'map<some_container_type, int, float, char, some_fn>(const some_container_type&, int, int, int)'
   29 |     map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:16:97: note: candidate: 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
   16 | template <typename C, typename ... T, void fn(const typename C::value_type &, T ... args)> void map(const C & c, T ... args) {
      |                                                                                                 ^~~
<source>:16:97: note:   template argument deduction/substitution failed:
<source>:29:56: error: type/value mismatch at argument 2 in template parameter list for 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
   29 |     map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:29:56: note:   expected a type, got 'some_fn'

Microsoft Visual C++ 编译器 (19.24.28314) 提供更具描述性的错误消息:

error C3547: template parameter 'fn' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'map'

有人可以解释我是否以及如何以惯用方式完成map 接受任意参数以将这些参数转发给fn

我知道我可以将fn 作为参数传递给map 函数,而不是将其指定为模板 的参数,但出于与内联相关的原因为了更好地理解 C++ 模板,我想保留 fn template 而不是 function 参数。

我也不想使用任何库,包括标准库(我在上面的示例中显示的std 的用途只是为了澄清问题)。我知道库中的某处有“函子”和“转发”,但我想它们也是用 C++ 编写的,所以我很好奇我的问题是否可以在没有任何库的情况下解决。

【问题讨论】:

  • Visual Studio 提供信息丰富的消息:error C3547: template parameter 'fn' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'map'
  • @S.M.确实如此,我现在使用 VS 2019 进行了测试。可以说,它比 godbolt.org 上的 GCC 给我的错误信息更好。我会尝试投入时间,看看有什么我能做的......

标签: c++ templates variadic-templates


【解决方案1】:

解决这个问题的一个简单方法是推导函数的非类型模板参数,并重新排序模板参数列表

template <typename C, auto fn, typename ... T> 
void map(const C & c, T ... args) {
    for(auto i : c) {
        fn(i, args...);
    }
}

然后这样称呼它

map<some_container_type, some_fn, int, float, char>(sc, 1, 2.0f, '3');

这是demo


您也可以将fn 移动到模板参数列表的开头。

template <auto fn, typename C, typename ... T> 
void map(const C & c, T ... args) {
    for(auto i : c) {
        fn(i, args...);
    }
}

现在由于CT 可以从函数参数中推导出来,这使得调用站点更加清晰

map<some_fn>(sc, 1, 2.0f, '3');

这是demo

【讨论】:

  • 谢谢,问题解决了。顺便说一下,如果C 模板参数放在fn 参数之后,则可以省略map 模板实例化的对应参数(some_container_type)。此外,模板中的参数包 (int, float, char) 对应的所有三个参数也可以在实例化中省略。基本上map&lt;some_fn&gt;(sc, 1, 2.0f, '3') 也是有效的。但我喜欢你的回答如何为我的程序工作提供最小更改!
  • @amn 是的,可以进行一些改进;正如你所说,我想主要解决你面临的问题。不过,我也会将该建议添加到答案中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-30
  • 2013-10-28
  • 2014-09-13
  • 2017-02-03
  • 2014-10-31
相关资源
最近更新 更多