模板函数的模板函数参数“忽略候选模板:无法匹配...”

coc*_*oco 2 c++ templates variadic-templates c++17

概述

我正在尝试将 lambda 传递给采用模板函数类型的模板函数。编译时出现错误candidate template ignored: could not match...

但是,当我尝试将相同的 lambda 传递给采用模板函数类型的模板类时,它会编译并运行。

问题

  1. 为什么这适用于模板类而不适用于模板函数?
  2. 有办法让这个工作吗?
  3. 当我使用模板函数尝试此操作时,模板函数类型的额外预期参数是什么(有关更多详细信息,请参阅下文)?

代码

考虑以下代码 (c++17)

#include <functional>

// define the template function type
template<typename T, typename...S>
using compose_fn_t = std::function<T(S...)>;


// define a template function which accepts the template function type
template<typename T, typename... S>
void compose(compose_fn_t<T, S...> fn) {};

// define a template class which accepts the template function type
template<typename T, typename... S>
class Compose {
public:
  Compose(compose_fn_t<T, S...> fn) {};
};

// some arbitrary types
struct A{};
struct B{};
struct C{};

int main() {

  compose<A, B, C>
    ([] (B b, C c) -> A { return {}; });  // this will not compile!

  Compose<A, B, C>
    ([] (B b, C c) -> A { return {}; });  // this compiles and runs correctly!

  return 0;
}

Run Code Online (Sandbox Code Playgroud)

当我使用编译时compose<A, B, C>,它会抛出以下错误

$ g++ -std=c++17 -o main main.cpp                                                                                                  
main.cpp:18:3: error: no matching function for call to 'compose'
  compose<A, B, C>
  ^~~~~~~~~~~~~~~~
main.cpp:8:6: note: candidate template ignored: could not match 'function<A (B, C, type-parameter-0-1...)>' against '(lambda at main.cpp:19:6)'
void compose(compose_fn_t<T, S...> fn) {
     ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

type-parameter-0-1模板函数类型 ( ) 所期望的附加类型是什么compose_fn_t

wal*_*nut 5

如果您为函数调用指定模板参数列表(如 中所示)compose<A, B, C>,则如果模板参数多于参数,则该列表被视为部分。

调用该函数仍然会对剩余的模板参数进行模板参数推导。

在您的情况下,参数包的其余参数是根据compose_fn_t参数推导的(前三个模板参数已经确定),但是失败了,因为 lambda 无法推导为类型std::function

您需要将函数参数中使用的模板参数强制放入非推导上下文中以避免这种情况。一种方法是使用

template<typename T, typename... S>
void compose(typename std::type_identity<compose_fn_t<T, S...>>::type fn) {};
Run Code Online (Sandbox Code Playgroud)

因为范围解析运算符左侧的所有内容::都是不可推导的。但这也意味着,如果没有模板参数列表,您将无法调用该函数。

std::type_identity是 C++20 功能,但您可以轻松实现自己的功能。它除了返回其成员中赋予它的类型之外什么也不做type

template<typename T>
struct type_identity {
    using type = T;
};
Run Code Online (Sandbox Code Playgroud)

或者,通过转发引用来获取参数,并将其转发到std::function函数体中的构造中,避免任何推导:

template<typename T, typename... S, typename F>
void compose(F&& f) {
    compose_fn_t<T, S...> fn{std::forward<F>(f)};
    // Use fn as before
};
Run Code Online (Sandbox Code Playgroud)

这对于类模板来说不是问题,因为只有在根本没有提供模板参数列表的情况下才会执行类模板参数推导 (CTAD)。


您还可以使用 CTAD onstd::function为您选择正确的std::function专业化,而无需重复类型:

compose(std::function{[] (B b, C c) -> A { return {}; }});
Run Code Online (Sandbox Code Playgroud)

您还可以将此结构移至定义中compose

template<typename F>
void compose(F&& f) {
    auto fn = std::function{std::forward<F>(f)};
    // use fn here as before
};
Run Code Online (Sandbox Code Playgroud)

以便调用

compose([] (B b, C c) -> A { return {}; });
Run Code Online (Sandbox Code Playgroud)

足够了。

std::function请注意,这两种情况都不适用于通用 lambda,并且还要注意,如果您用别名替换,它们也不起作用compose_fn_t,因为 CTAD 不是对别名完成的。