【问题标题】:C++ 11: overload resolution and SFINAEC++ 11:重载决议和 SFINAE
【发布时间】:2015-08-13 09:36:27
【问题描述】:

我正在学习 SFINAE,这是我第一次尝试仅针对可以使用 std::ostream 输出的那些类型打印“YES”(暂时忘记 std::operator<<(std::ostream &, T)...):

template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }

template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
    &std::ostream::operator<<))>
void f(const T &) { std::cout << "YES" << std::endl; }

尽管它们似乎与 f(std::vector&lt;int&gt;()) 一起工作(产生“否”),但编译器抱怨 f(0) 模棱两可:http://ideone.com/VljXFh

prog.cpp:16:5: error: call of overloaded 'f(int)' is ambiguous
  f(0);
     ^
prog.cpp:6:6: note: candidate: void f(const T&) [with T = int]
 void f(const T &) { std::cout << "NO" << std::endl; }
      ^
prog.cpp:10:6: note: candidate: void f(const T&) [with T = int; int SFINAE = 8]
 void f(const T &) { std::cout << "YES" << std::endl; }
      ^

如何修复我的代码? “YES”版本不比“NO”版本更具体吗?

澄清

所有f(0)f(0.)f(true) 都失败并出现相同的“模棱两可”错误。我正在寻找一种适用于std::ostream::operator&lt;&lt; 接受的所有类型的解决方案。理想情况下,它不应该依赖于定义“污染”命名空间的辅助类型。

【问题讨论】:

    标签: c++ c++11 template-meta-programming sfinae overload-resolution


    【解决方案1】:

    NO 版本对int 仍然有效,并且没有适用的偏序在两个重载之间进行选择,因此调用不明确。

    一种简单的消除歧义的方法是在函数中添加一个额外的标签参数:

    template <typename T>
    void f(const T &, char) { std::cout << "NO" << std::endl; }
    //                ^^^^
    
    template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
        &std::ostream::operator<<))>
    void f(const T &, int) { std::cout << "YES" << std::endl; }
    //                ^^^
    

    现在,当您调用该函数时,只需传递一个额外的0(或编写一个辅助函数来为您完成)。如果 SFINAE 保护函数有效,则将首选它,因为 intchar 更适合 0。请参阅this article 了解表达这种消歧的更简洁的方式。

    或者,您可以编写一个特征来检查运算符是否对给定类型有效,然后使用std::enable_if&lt;check&lt;T&gt;&gt;std::enable_if&lt;!check&lt;T&gt;&gt; 来避免消除歧义。

    顺便说一句,您可以为这种 SFINAE 使用 decltype 和尾随返回类型,我认为它看起来更简洁:

    template <typename T>
    void f(const T &, char) { std::cout << "NO" << std::endl; }
    
    template <typename T>
    auto f(const T &t, int) -> decltype(std::declval<std::ostream&>() << t, void())
    { std::cout << "YES" << std::endl; }
    

    当我们获得 C++ 概念后,您将能够执行以下操作(这适用于启用概念的 GCC):

    template <typename T>
    concept bool Outputtable = requires (T t, std::ostream o) { o << t; };
    
    template <typename T>
    void f(const T &) { std::cout << "NO" << std::endl; }
    
    template <Outputtable T>
    void f(const T &) { std::cout << "YES" << std::endl; }
    

    【讨论】:

    • 对于标记,我更喜欢使用struct overload_priority_high : overload_priority_low {}; 之类的东西,它比int vs charlong 更具可读性。
    • @Jarod42 我同意,这就是我链接到 Xeo 关于该主题的文章的原因。我可以编辑一些额外的东西,但答案已经有点冗长和切题了!
    • 您添加另一个参数的解决方案似乎对charint 过于具体。事实上,f(0.)f(true) 也会出现“模棱两可”的错误。否则是否必须定义特征?如果可能的话,我更喜欢“单线”解决方案。
    • 按照我的建议,最好编写一个简单的转发函数来隐藏客户端代码中的消歧参数。
    • 或执行 Jarod42 建议的操作或我链接到的文章中的操作。
    【解决方案2】:

    从 C++17 开始,可以使用 std::void_t 来描述它:

    template<typename, typename = std::void_t<>>
    struct enables_ostream_output : std::false_type {};
    
    template<typename Type>
    struct enables_ostream_output<
        Type,
        std::void_t<decltype(std::declval<std::ostream>() << std::declval<Type>())>
    > : std::true_type {};
    

    结合经典的std::enable_if:

    template <typename Type>
    typename std::enable_if<!enables_ostream_output<Type>::value>::type
    f(const Type &) { std::cout << "NO" << std::endl; }
    
    template <typename Type>
    typename std::enable_if<enables_ostream_output<Type>::value>::type
    f(const Type &) { std::cout << "YES" << std::endl; }
    

    @TartanLlama 推荐的另一种选择是使用 std::(experimental::)is_detected 作为:

    template<typename Type>
    using ostream_output_t = decltype(std::declval<std::ostream>() << std::declval<Type>());
    

    然后:

    template <typename Type>
    typename std::enable_if<!std::is_detected<ostream_output_t, Type>::value>::type
    f(const Type &) { std::cout << "NO" << std::endl; }
    
    template <typename Type>
    typename std::enable_if<std::is_detected<ostream_output_t, Type>::value>::type
    f(const Type &) { std::cout << "YES" << std::endl; }
    

    【讨论】:

    • 您可能会从std::is_detected 或Concepts 中获得更好的版本。
    • @TartanLlama :(
    • 是否必须使用enable_if&lt; ! ...&gt; 明确禁用“NO”版本?
    • @nodakai 是的,否则会模棱两可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 1970-01-01
    • 2011-03-26
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    相关资源
    最近更新 更多