【问题标题】:Compiler cannot deduce type of template function?编译器无法推断模板函数的类型?
【发布时间】:2020-09-05 13:39:19
【问题描述】:

我有以下代码示例:

template<typename T>
void print(T t) {
    std::cout << t << std::endl;
}

template<typename Key, typename Value, typename F>
void traverse(std::map<Key, Value>& m, F f) {
    for (auto&& [key, value] : m) {
        f(value);
    }
}

int main()
{
    std::map<int, std::string> M;
    // initializing the map
    traverse(M, print);
}

这里编译器出错并显示以下消息:

could not deduce template argument for 'F'

显式的traverse(M, print&lt;std::string&gt;); 解决了这个问题。
我的问题真的是为什么编译器不能推断打印函数的模板类型?

据我了解,所有的编译时间信息都是可用的。

【问题讨论】:

  • 我没有参考标准,但我认为模板参数推导发生在实例化之前(为了 SFINAE 和其他东西工作)。
  • 函数模板不是函数。您要求编译器推断生成具有不同类型的函数的工厂的类型。例如。 print&lt;string&gt; 的类型为 void(string),而 print&lt;double&gt; 的类型为 void(double)。作为模板,print 本身没有类型。因此编译器无法推断其类型。
  • 如果您改为使用auto print = [](auto t){ std::cout &lt;&lt; t &lt;&lt; std::endl; },那么您将拥有F 的可推导类型(然后在调用时推导std::string

标签: c++ templates c++17 type-deduction


【解决方案1】:

模板参数推导基于函数的模板参数、函数的签名和提供的参数类型。而已。即使忽略 print 是一个模板函数(因此没有可用于推导的类型)这一事实,traverse 的签名中也没有任何内容可以让编译器知道该类型推断将是print&lt;std::string&gt; 的类型。任何此类信息都将在函数的定义中。

一个尚不存在且在traverse实例化后才能存在的定义。在您拥有模板参数之前,您无法实例化模板。这就是模板参数推导的目的。

另外,如前所述,print 不是函数;它是一个模板的名字。它没有类型;它甚至不是一个函数。它是一种基于模板参数生成函数的方法。即使您尝试将print 传递给不是模板的函数,代码仍然无法工作。只有当您调用它们时,编译器才能对函数执行模板参数推导,而不是当您将它们作为函数参数传递时。在所有非推导的情况下,您必须直接提供模板参数。

【讨论】:

    【解决方案2】:

    并非所有必要的编译时间信息都可用,特别是 print 模板实例化参数。

    traverse 是一个函数模板,第二个参数接受任何参数类型,但是名称print 本身并不表示任何特定类型,只是一个模板名称,编译器无法推断出哪个具体实例化您打算传递给它的模板。

    显然您希望编译器从M 类型的模板参数中推断出printprint&lt;std::string&gt; 实例化。为此,您可以在traverse 模板中指定这一点,使f 参数的类型依赖于m 的类型,删除不相关的typename F,例如,像这样:

    template<typename Key, typename Value>
    void traverse(std::map<Key, Value>& m, void (*f)(Value)) {
        for (auto&& [key, value] : m) {
            f(value);
        }
    }
    

    【讨论】:

      【解决方案3】:

      编译器无法推断出print 的类型,因为它忽略了traverse 的主体和所有不相关的参数。当您调用 traverse(M, print) 时,编译器会尝试确定打印类型,就像您刚刚调用了 example 一样:

      template<typename F>
      void example(F f);
      
      example(print);
      

      你可以通过改变traverse的类型来编译你的代码:

      template<typename Key, typename Value>
      void traverse(std::map<Key, Value>& m, void(*f)(Value));
      

      或通过指定特定的print

          traverse(M, print<std::string>);
      

      【讨论】:

      • 感谢您的提示。实际上只有函数指针版本通过编译,std::function 版本抛出以下错误: cannot convert argument 2 from 'void (__cdecl *)(T)' to 'std::function'
      猜你喜欢
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      • 2018-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多