重复定义?

Duplicate Definitions?

我有以下代码:

#include <iostream>

/*
template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {
    std::cout << "SFINAE" << std::endl;
}
*/

template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {
    std::cout << "SFINAE" << std::endl;
}

int main() {
    test<int>();
    test<double>();
}

编译器抱怨

test_function_templ.cpp:21:6: error: redefinition of ‘template<class A, class> void test()’
   21 | void test() {
      |      ^~~~
test_function_templ.cpp:16:6: note: ‘template<class A, class> void test()’ previously declared here
   16 | void test() {
      |      ^~~~
test_function_templ.cpp: In function ‘int main()’:
test_function_templ.cpp:27:15: error: no matching function for call to ‘test<double>()’
   27 |  test<double>();
      |               ^
test_function_templ.cpp:16:6: note: candidate: ‘template<class A, class> void test()’
   16 | void test() {
      |      ^~~~
test_function_templ.cpp:16:6: note:   template argument deduction/substitution failed:
In file included from /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/move.h:57,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/nested_exception.h:40,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/exception:148,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/ios:39,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/ostream:38,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/iostream:39,
                 from test_function_templ.cpp:1:
/opt/rh/devtoolset-10/root/usr/include/c++/10/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]’:
test_function_templ.cpp:15:20:   required from here
/opt/rh/devtoolset-10/root/usr/include/c++/10/type_traits:2554:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
 2554 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~

如果我使用第一组函数模板(在代码中注释)test(),它会按预期编译和运行。

问题

  1. 我想到了第二组函数模板,调用test<int>()会实例化test<int, void>(),调用test<double>()会实例化test<double, void>()。但是编译器好像看到了两个重复的模板函数?

  2. 为什么第一套功能模板没有问题,而第二套却有问题?

描述了第一个片段的问题 here(查看 /* WRONG *//* RIGHT */ 代码片段如何分别映射到您的注释代码和未注释代码)。

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).


这是我对为什么会这样的理解。

当编译器看到这个(正确的版本)

template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {}

template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {}

它没有看到重新声明,因为它不知道两个 std::enable_if_t 是否会解析为同一类型。如果它知道,那么这将是一个硬编译时错误,就像这是一个错误:

template <class A, bool = true>
void test() {}

template <class A, bool = false> // no matter the value; signature is the same
void test() {}

这不排除在替换级别可能发生歧义。比如这些重载声明,原则上是没有问题的

template <class A, std::enable_if_t<std::is_convertible_v<A, double>, bool> = true>
void test() {}

template <class A, std::enable_if_t<std::is_convertible_v<A, int>, bool> = true>
void test() {}

但是一旦调用 test<int>(),就会出现歧义,因为编译器将能够“成功”实例化每个重载,这都会导致 template<int, bool = whatever>,这使他们模棱两可。

关于版本错误:

template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {}

template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {}

问题是,甚至在看到它是如何使用之前,编译器就已经报错了,因为函数模板等价 中没有考虑默认模板参数。事实上,一个更简单的例子显示了前面片段的问题:

template <class A, typename = typename A::foo>
void test() {}

template <class A, typename = typename A::bar>
void test() {}

请注意,与前面的代码片段完全一样,在最后一个代码片段中,每个重载本身都是正确的,直到您尝试将它与默认参数的表达式不适用的特定 A 一起使用'有意义,因此导致硬错误。

但是如果将两个重载放在一起,就会产生歧义,因为默认模板参数不在函数模板等价性中考虑