【问题标题】:Function template argument deduction (class vs funtion template)函数模板参数推导(类与函数模板)
【发布时间】:2018-12-05 03:19:55
【问题描述】:

你能帮我理解为什么参数推导适用于类模板而不适用于函数模板吗?

如果我理解正确,类模板定义了一个函数,所以当我调用时编译器可以进行隐式转换,但是对于函数模板,目前没有函数定义,所以隐式演员没有发生。

但我不明白为什么编译器不能创建函数定义然后应用隐式转换?

#include <functional>

template<typename ...ARGS>
class Test1
{
public:
    void add(const std::function<void(ARGS...)>&) {}
};

class Test2
{
public:
    template<typename ...ARGS>
    void add(const std::function<void(ARGS...)>&) {}
};

void func(int) {}

int main()
{
    Test1<int> test1;
    test1.add(func);

    Test2 test2;
    test2.add<int>(func);
}

错误是:

在函数'int main()'中:

   25:24:错误:没有匹配函数调用“Test2::add(void (&)(int))”

   25:24:注意:候选人是:

   14:10:注意:模板 void Test2::add(const std::function&)

   14:10:注意:模板参数扣除/替换失败:

   25:24:注意:不匹配的类型 'const std::function' 和 'void(int)'

【问题讨论】:

    标签: c++ templates std-function template-argument-deduction


    【解决方案1】:

    在第一种情况下,您正在显式实例化类模板Test1。这意味着其add 成员的函数声明是使用签名add(const std::function&lt;void(int)&gt;&amp;) 生成的。当编译器随后尝试解析test1.add(func) 时,只有一个候选者。由于 std::function&lt;void(int)&gt; 可以从 void(int) 函数隐式构造,签名匹配,编译器只是实例化成员函数定义,一切都很好。

    在第二种情况下,编译器必须执行模板参数推导/替换以查看它是否可以“使用”add 模板。您可能认为指定 int 会确定模板参数,因此不需要推导,但事实并非如此:您的意思可能是部分指定模板参数,例如 here。换句话说,您可能会尝试使用比您明确指定的参数更多的参数来实例化函数模板,至少编译器不知道您是否这样做。所以它仍然必须尝试匹配 std::function&lt;void(ARGS...)&gt; 的类型(或者更准确地说,std::function&lt;void(int, ...)&gt;void(int),它不能,因为扣除时不考虑隐式转换。

    简而言之:指定显式模板参数不会阻止可变参数函数模板的模板参数推导。

    注意:我不是 100% 坚定地使用确切的术语,任何语言律师来纠正我都非常感谢!

    编辑:我主要基于我读到的here

    【讨论】:

    • @Aconcagua 我不认为你是正确的。 This 例如说“成员函数在被调用时被实例化”。编译器完全定义了类,但这只是拼写出成员函数签名。
    • 嗯,“确切的术语”问题...我们可以说 signature 已经实例化了吗?要点是:随着类的实例化,成员函数是固定的(这是我试图强调的更强......)。不会再有任何模板扣除,所以转换是合法的。为了便于理解,代码是在类实例化时已经生成还是仅在第一次调用函数时生成并不重要。
    • 我的意思是,虽然在技术上是正确的,但我对“实例化”这个术语感到有些不舒服,因为在第二种情况下,我们也实例化了函数(这次包括签名),这可能是一个来源误会……
    • 我实际上试图在答案中涵盖这个确切的区别:“这意味着为它的add 成员生成了函数声明”和“编译器只是实例化了该成员函数模板定义”。我不知道在提到模板实例化时声明/定义是否是正确的术语,但我试图尽可能清楚地说明它。虽然您可能是对的,实际代码生成与函数解析无关。
    • 你第一段的最后一句没有意义:你没有成员函数模板,它只是一个简单的成员函数。你的术语很好,虽然你真的不想使用语言律师术语,因为它与“正常”术语有点不同。 ;)
    【解决方案2】:

    我最初的原因是为什么以下 sn-ps 工作是错误的,但正如 @NathanOliver 帮助我(见下文),这里是修改后的解释:在模板参数推导期间,不执行类型转换。将函数指针传递给接受 std::function 参数的函数需要这样的转换。为了规避这个问题,你可以像这样调用方法

    test2.add(std::function<void(int)>(func));
    

    或将Test2的定义调整为

    class Test2
    {
        template<typename ...ARGS>
        void add(void(*)(ARGS...)) {}
    }
    

    与原始调用一起使用

    test2.add<int>(func);
    

    在这两个示例中,都不需要转换。调用Test1::add成功是因为在调用该方法之前已经进行了模板类型推导,因此可以进行转换。

    另请注意,当Test2 使用一个模板参数声明时,也会出现同样的问题,

    class Test2
    {
        template<typename T>
        void add(const std::function<void(T)>&) {}
    }
    

    在调用方有以下用例:

    test2.add<int>(func); // Conversion ok, function template specified
    test2.add(std::function<void(int)>(func)); // Type deduction, no conversion
    test2.add(func); // Error, conversion AND type deduction
    

    【讨论】:

    • 很遗憾,您的理由不正确,至少部分不正确。问题是函数指针不是std::function。因为它不是编译器,所以不会尝试将其转换为一种来确定模板类型。基本规则:模板参数推导不进行任何转换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-15
    • 1970-01-01
    • 2021-08-31
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多