通过模板参数列表区分模板函数

Hao*_*shu 7 c++ templates

我创建了4个类:

class C1 {};
class C2 {};
class C3 {};
class C4 {};
Run Code Online (Sandbox Code Playgroud)

以及基于这些类的模板函数:

template<class T1, class T2>
T2 F() {
    std::cout << "F<T1, T2>" << std::endl;
    return T2();
}

template<class T>
T F();

template<>
C1 F<C1>() {
    std::cout << "F<C1>" << std::endl;
    return F<C3, C1>();
}

template<>
C2 F<C2>() {
    std::cout << "F<C2>" << std::endl;
    return F<C4, C2>();
}
Run Code Online (Sandbox Code Playgroud)

这给了我编译错误:

explicit specialization 'C1 F<C1>(void)' is not a specialization of a function template
explicit specialization 'C2 F<C2>(void)' is not a specialization of a function template
Run Code Online (Sandbox Code Playgroud)

但是,如果我将第一个函数的名称更改F为不同的名称F1

template<class T1, class T2>
T2 F1() {
    std::cout << "F<T1, T2>" << std::endl;
    return T2();
}

template<class T>
T F();

template<>
C1 F<C1>() {
    std::cout << "F<C1>" << std::endl;
    return F1<C3, C1>();
}

template<>
C2 F<C2>() {
    std::cout << "F<C2>" << std::endl;
    return F1<C4, C2>();
}
Run Code Online (Sandbox Code Playgroud)

或者将这些函数的返回类型更改为相同(例如int):

template<class T1, class T2>
int F() {
    std::cout << "F<T1, T2>" << std::endl;
    return 5;
}

template<class T>
int F();

template<>
int F<C1>() {
    std::cout << "F<C1>" << std::endl;
    return F<C3, C1>();
}

template<>
int F<C2>() {
    std::cout << "F<C2>" << std::endl;
    return F<C4, C2>();
}
Run Code Online (Sandbox Code Playgroud)

然后编译成功,我可以成功调用:

F<C1>();
F<C2>();
Run Code Online (Sandbox Code Playgroud)

并得到预期的输出:

F<C1>
F<T1, T2>
F<C2>
F<T1, T2>
Run Code Online (Sandbox Code Playgroud)

问题是为什么第一个代码不能编译,但第二个和第三个代码可以编译。

https://en.cppreference.com/w/cpp/language/function_template我发现:

具有相同返回类型和相同参数列表的两个函数模板是不同的,可以通过显式模板参数列表来区分。

我想这就是第三个代码编译的原因,因为返回类型是相同的。但它并不能解释为什么第一个代码不能编译只是因为返回类型不同。

即使这是因为返回类型不能不同,它也不能解释为什么第二个代码编译为F<C1>F<C2>具有不同的返回类型。

use*_*522 4

当你写下声明时

template<>
C1 F<C1>() { /*...*/ };
Run Code Online (Sandbox Code Playgroud)

编译器必须找出F您想要专门化的函数模板。这是通过模板参数推导来完成的,类似于函数调用中的工作方式。特别是,不假设模板参数列表<C1>是特化的完整模板参数列表。它可以是部分的,也可以像函数调用一样被省略。

候选人是模板1:

template<class T1, class T2>
T2 F() {
    std::cout << "F<T1, T2>" << std::endl;
    return T2();
}
Run Code Online (Sandbox Code Playgroud)

和模板2:

template<class T>
T F();
Run Code Online (Sandbox Code Playgroud)

虽然函数调用中的推导考虑调用参数和函数形参对,但对于显式特化匹配,函数模板本身的类型会与声明的特化类型进行比较。(参见[temp.deduct.decl]。)

这里的专业类型是C1()。模板1的类型为T2(),模板2的类型为T()。您在专业化中明确指定T = C1,因此无需推断任何内容并且类型匹配。您还T1 = C1明确指定并T2()根据C1()给出进行推导T2 = C1

因此,两个模板都匹配:对于模板 1,它与专门化匹配F<C1, C1>;对于模板 2,它与专门化匹配F<C1>

因此,目前您所声明的专业还不清楚。

然后考虑函数模板的偏序来决定模板 1 还是模板 2更专业,因此应该是首选。为此,再次考虑(转换后的)模板的完整类型,请参阅[temp.deduct.partial]/3.3。在部分排序中,显式指定的模板参数不相关,但T()可以在两个方向上进行推导T2()。唯一的问题是,它T1不会被推导出任何东西,但如果模板参数没有出现在类型中,则在部分排序中会被特别忽略(请参阅[temp.deduct.partial]/12)。因此,部分排序也会考虑两者同样专业化。

因此,您想要专门化哪个函数模板是不明确的。

调用函数模板时不会遇到同样的问题,因为返回类型不参与函数调用中的推导。