为什么我必须调用 operator<< 作为 SFINAE 与 void_t 一起工作的方法?

Why do I have to call operator<< as a method for SFINAE to work with void_t?

我正在尝试定义一个 has_ostream_operator<T> SFINAE 测试来检查我是否可以计算出给定的类型。我让它工作,但前提是在我对 has_ostream_operator 的定义中,我将 operator<< 称为方法而不是中缀运算符。换句话说,这有效:

decltype(std::declval<std::ostream>().operator<<(std::declval<T>()))>

这不是:

decltype(std::declval<std::ostream>() << std::declval<T>())>

下面的测试用例(也可以在http://coliru.stacked-crooked.com/a/d257d9d6e0f3f6d9看到)。请注意,我包含了 void_t 的定义,因为我只使用 C++14。

#include <iostream>

namespace std {

    template<class...>
    using void_t = void;

}

template<class, class = std::void_t<>>
    struct has_ostream_operator : std::false_type {};

template<class T>
struct has_ostream_operator<
    T,
    std::void_t<
        decltype(
            std::declval<std::ostream>().operator<<(std::declval<T>()))>>
    : std::true_type {};

struct Foo {};

template<class X>
    void print(
        const X& x,
        std::enable_if_t<has_ostream_operator<X>::value>* = 0)
{
    std::cout << x;
}

template<class X>
    void print(
        const X&,
        std::enable_if_t<!has_ostream_operator<X>::value>* = 0)
{
    std::cout << "(no ostream operator<< implementation)";
}

int main()
{
    print(3); // works fine
    print(Foo()); // this errors when using infix operator version
    return 0;
}

你需要

std::declval<std::ostream&>() << std::declval<T>()
//                       ^

std::declval<std::ostream>() 是右值;您正在为右值流击中包罗万象的 operator<< 重载。

如果使用中缀表示法,则会找到右值流插入器,因为 declval returns 右值本身; [ostream.rvalue]:

template <class charT, class traits, class T>
basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>&& os, const T& x);

此重载当前接受 x 的所有参数。我已提交 LWG #2534,如果得到相应解决,将使您的初始代码按预期工作。

临时解决方法是使 declval return 成为左值引用,即将模板参数调整为一个:

std::declval<std::ostream&>() << std::declval<T>()

我假设您的 "infix" 版本使用了这个表达式:

std::declval<std::ostream>() << std::declval<T>()

匹配 Foo 的原因是因为第一部分 declval<ostream>() 产生类型 ostream&& 的右值。这匹配一个非成员 operator<<:

template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os, 
                                            const T& value );

该重载只是转发呼叫:

Calls the appropriate insertion operator, given an rvalue reference to an output stream object (equivalent to os << value).

您应该直接检查。所有的重载都采用 ostream by lvalue 引用,所以你也应该测试一下:

std::declval<std::ostream&>() << std::declval<T>()