完全专用的模板函数是否违反了常规函数的 ODR?
Does fully specialized template function violate ODR with a regular function?
我刚刚意识到这段代码可以安全编译,在 g++
和 clang
上没有任何警告。 (给出 --std=c++14 --Wall
)
#include <iostream>
template <typename T>
void foo(const T& a, const T& b)
{
std::cout << "1. Template version called.\n";
}
template <>
void foo(const int& a, const int& b)
{
std::cout << "2. Template specialized version called.\n";
}
void foo(const int& a, const int& b)
{
std::cout << "3. Regular function version called.\n";
}
int main()
{
// Prints: 3. Regular function version called.
foo(4, 2);
}
但是我不确定,根据标准,2. Template specialized version
和3. Regular function version
是否违反了ODR。
那么,这是否违反了 ODR?
如果不是,是否保证 foo(4,2)
总是调用 3. Regular function version
?
还是依赖于编译器?
简短回答:不,它不违反 ODR,是的,只要您使用符合 C++ 标准的编译器,就可以保证调用常规函数。
当调用 foo
时,编译器首先通过查找名称 foo
来制作候选函数列表。候选人之一是常规功能。它还通过模板类型推导,最终发现您的专业化是重载决议的第二个候选者。
之后,编译器通过匹配参数的数量并测试参数和您提供的参数之间是否存在隐式转换来生成一组可行的候选者。两个候选者(常规函数和专用模板)都通过了这个阶段并且可行。
然后,编译器通过遵循描述的一组规则来决定哪些可行的候选者是最好的,例如here。通常,它会选择参数类型与参数类型最匹配的候选人(规则 1.-3,如链接页面中所示)。但是,因为您的参数类型完全相同,所以会转到规则 4,即 non-templated 函数优先于 template-specalizations。所以选择了常规函数!
现在回答您关于 ODR 违规的问题:编译器为模板分配了一个与常规函数不同的符号。如果您从上面编译程序并查看导出的(损坏的)符号,您会看到类似
的内容
void foo<int>(int const&, int const&)
foo(int const&, int const&)
所以这两个函数都是导出的,也可以从其他翻译单元调用(根据与上述相同的规则)。
注意 关于函数模板特化的附加说明。有理由更喜欢重载函数而不是专门的函数模板。如果提供一个专业化,其中两个或多个模板可以是“父模板”,您可以 运行 产生奇怪的效果,其中声明顺序会影响实际结果。您可以在 this question.
的答案中找到有关此主题的更多信息和示例
我刚刚意识到这段代码可以安全编译,在 g++
和 clang
上没有任何警告。 (给出 --std=c++14 --Wall
)
#include <iostream>
template <typename T>
void foo(const T& a, const T& b)
{
std::cout << "1. Template version called.\n";
}
template <>
void foo(const int& a, const int& b)
{
std::cout << "2. Template specialized version called.\n";
}
void foo(const int& a, const int& b)
{
std::cout << "3. Regular function version called.\n";
}
int main()
{
// Prints: 3. Regular function version called.
foo(4, 2);
}
但是我不确定,根据标准,2. Template specialized version
和3. Regular function version
是否违反了ODR。
那么,这是否违反了 ODR?
如果不是,是否保证 foo(4,2)
总是调用 3. Regular function version
?
还是依赖于编译器?
简短回答:不,它不违反 ODR,是的,只要您使用符合 C++ 标准的编译器,就可以保证调用常规函数。
当调用 foo
时,编译器首先通过查找名称 foo
来制作候选函数列表。候选人之一是常规功能。它还通过模板类型推导,最终发现您的专业化是重载决议的第二个候选者。
之后,编译器通过匹配参数的数量并测试参数和您提供的参数之间是否存在隐式转换来生成一组可行的候选者。两个候选者(常规函数和专用模板)都通过了这个阶段并且可行。
然后,编译器通过遵循描述的一组规则来决定哪些可行的候选者是最好的,例如here。通常,它会选择参数类型与参数类型最匹配的候选人(规则 1.-3,如链接页面中所示)。但是,因为您的参数类型完全相同,所以会转到规则 4,即 non-templated 函数优先于 template-specalizations。所以选择了常规函数!
现在回答您关于 ODR 违规的问题:编译器为模板分配了一个与常规函数不同的符号。如果您从上面编译程序并查看导出的(损坏的)符号,您会看到类似
的内容void foo<int>(int const&, int const&)
foo(int const&, int const&)
所以这两个函数都是导出的,也可以从其他翻译单元调用(根据与上述相同的规则)。
注意 关于函数模板特化的附加说明。有理由更喜欢重载函数而不是专门的函数模板。如果提供一个专业化,其中两个或多个模板可以是“父模板”,您可以 运行 产生奇怪的效果,其中声明顺序会影响实际结果。您可以在 this question.
的答案中找到有关此主题的更多信息和示例