【问题标题】:Duplicate Definitions? [duplicate]重复定义? [复制]
【发布时间】:2021-12-26 09:33:24
【问题描述】:

我有以下代码:

#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&lt;int&gt;() 将实例化test&lt;int, void&gt;(),调用test&lt;double&gt;() 将实例化test&lt;double, void&gt;()。但是编译器似乎看到了两个重复的模板函数?

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

【问题讨论】:

    标签: c++ c++11 c++17 template-meta-programming sfinae


    【解决方案1】:

    第一个 sn-p 的问题描述为 here(请参阅代码的 /* WRONG *//* RIGHT */ sn-ps 如何分别映射到您的注释和未注释代码)。

    一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。这不起作用,因为声明被视为同一函数模板的重新声明(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&lt;int&gt;(),就会出现歧义,因为编译器将能够“成功”实例化每个重载,这都会导致template&lt;int, bool = whatever&gt;,这会使它们产生歧义。

    关于错误的版本:

    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() {}
    

    问题在于,甚至在看到它是如何使用的之前,编译器就已经抱怨了,因为默认模板参数没有在函数模板等价中考虑。确实,一个简单得多的例子就说明了前面sn-p的问题:

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

    请注意,与之前的 sn-p 中一样,在最后的 sn-p 中,每个重载本身都是正确的,直到您尝试将其与特定的 A 一起使用,默认参数的表达式不会'没有意义,从而导致一个硬错误。

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

    【讨论】:

    • 感谢您的链接。我没有得到的是,在“错误”版本中,当调用 test&lt;int&gt;() 时,我认为只有 test&lt;int, void&gt;() 是从第一个函数模板实例化的,而第二个函数模板不会被实例化,因为替换失败std::enable_if_t&lt;std::is_same_v&lt;A, double&gt;&gt;。或者甚至在任何函数模板被实例化之前,就在函数模板声明上检查函数模板等价性?谢谢。
    • @HCSF,是的。试试把main里面的两行注释掉,还是一样的错误。
    • 我明白了。所以在错误的版本中,编译器在测试函数模板等效性阶段看到了两次template &lt;class A, class&gt; test()。在测试函数模板等效性阶段,编译器在正确的版本中看到了什么?
    • @HCSF,在正确的版本中,编译器会看到两个重载,但在替换时(例如,当您在 main 中调用 test&lt;whatever&gt;() 时)两者之一将失败,只留下另一个。
    • @HCSF,不确定所有可能的用例,但我看到它曾经只是禁用功能。我想你真的很想阅读this book
    猜你喜欢
    • 2015-03-21
    • 1970-01-01
    • 1970-01-01
    • 2017-01-18
    • 2012-07-06
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 2011-06-27
    相关资源
    最近更新 更多