为什么我的可变参数模板实例化不起作用?

Why does my variadic template instantiation not work?

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

尽管我的 map 没有 return 任何东西(这里不是因素),但如果传递给“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 一个 模板 而不是 函数 参数。

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

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

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