函数模板参数推导(类模板与函数模板的区别)

10

你能帮我理解为什么类模板可以进行参数推导,而函数模板不行吗?

如果我理解正确的话,类模板定义了一个函数,因此当我调用它时,编译器可以进行隐式转换,但是在函数模板的情况下,目前没有函数定义,所以隐式转换不会发生。

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

#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)'

2个回答

8
在第一种情况下,您正在显式实例化类模板Test1。这意味着其add成员的函数声明将生成具有签名add(const std::function<void(int)>&)。当编译器随后尝试解析test1.add(func)时,只有一个候选项。由于std::function<void(int)>可以从void(int)函数隐式构造,因此签名匹配,编译器仅实例化成员函数定义,一切都很好。
在第二种情况下,编译器必须执行模板参数推导/替换,以查看是否可以“使用”add模板。您可能认为指定int会固定模板参数,因此不需要推导,但事实并非如此:您可能意味着部分指定模板参数,请参见here的示例。换句话说,您可能正在尝试使用比您明确指定的更多参数来实例化函数模板,至少编译器不知道您是否这样做。因此,它仍然必须尝试匹配std::function<void(ARGS...)>(或更准确地说是std::function<void(int, ...)>void(int))的类型,但无法匹配,因为对于推导而言,隐式转换并未考虑在内。

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

注意:我对精确术语不是100%确定,欢迎任何语言专业人士纠正我!

编辑:我主要是基于我在这里读到的内容。


我的意思是,尽管在技术上是正确的,但我对"实例化"这个术语感到有些不舒服,因为在第二种情况下,我们也要实例化函数(这次包括签名),这可能是误解的源头... - Aconcagua
我实际上在回答中试图涵盖这个确切的区别:“这意味着为其add成员生成函数声明”,“编译器只是实例化成员函数模板定义”。我不知道在引用模板实例化时声明/定义是否是正确的术语,但我尽力让它尽可能清晰。虽然你可能是对的,实际代码生成与函数解析关系不大。 - Max Langhof
你第一段的最后一句话没有意义:你没有一个成员函数模板,它只是一个简单的成员函数。你的术语很好,但你真的不想使用法律术语,因为它与“正常”的术语有点不同。 ;) - Rakete1111
@Max "编译器只是实例化成员函数模板定义",对吗? - Rakete1111
@Max 是的,模板类的功能是被实例化的,但它并不是一个模板,就像作者所说的那样,但你不是 :) - Rakete1111
显示剩余6条评论

2

以下代码片段为什么有效的原因是我最初的推理是错误的,但是在@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

3
很遗憾,你的理解并不完全正确。问题在于函数指针不是std::function类型。因为它不是,编译器将不会尝试将其转换为std::function来确定模板类型。基本规则:在模板参数推断中不进行任何转换。 - NathanOliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接