使用std :: ostream的declval表达式(对于SFINAE)

Sil*_*ler 18 c++ sfinae

我正在尝试创建一个类型特征类来确定特定类型是否T可以通过<<运算符流式传输std::ostream.我正在使用简单的SFINAE技术.

最终,我试图评估替换失败的表达式是:

decltype(std::declval<std::ostream>() << std::declval<T>()) ;
Run Code Online (Sandbox Code Playgroud)

我的期望是,给定t类型Tstd::ostream实例的实例os,如果表达式 os << t格式错误,则应该发生替换失败.

但显然,无论类型如何,替代失败都不会发生在这里T.即使我只是声明typedef使用上面的decltype表达式,在SFINAE的上下文之外,它很乐意编译,即使T不能使用std::ostream.

例如:

struct Foo  { };

int main()
{
    // This compiles fine using GCC 4.9.2
    //
    typedef decltype(
        std::declval<std::ostream>() << std::declval<Foo>()
    ) foo_type;
}
Run Code Online (Sandbox Code Playgroud)

以上将使用GCC 4.9.2进行编译,这不是我预期的,因为<<运算符没有重载以使用类型Foo.当然,如果我说:

std::cout << Foo();
Run Code Online (Sandbox Code Playgroud)

...我收到编译器错误.那么为什么decltype上面的表达式甚至可以编译呢?

Tar*_*ama 16

C++ 11添加了以下operator<<重载:

template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os, 
                                            const T& value );
Run Code Online (Sandbox Code Playgroud)

这转发到标准插入运算符,它不能将rvalue引用绑定到std::ostreams,因为它们采用非const引用.因为std::declval<std::ostream>返回std::ostream&&,所以选择了这个重载,然后由于非常宽松的接口(即如果没有有效的底层插入操作符,这不是SFINAEd),你的decltype说明符可以工作.

简单的解决方法是使用std::declval<std::ostream&>().这将返回a std::ostream&,因此decltype说明符不会选择模板重载,并且编译时需要正常的插入操作符重载:

typedef decltype(
    std::declval<std::ostream&>() << std::declval<Foo>()
) foo_type;
Run Code Online (Sandbox Code Playgroud)

Clang输出:

main.cpp:8:39: error: invalid operands to binary expression ('std::basic_ostream<char>' and 'Foo')
        std::declval<std::ostream&>() << std::declval<Foo>()
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

Live Demo


这是一个更简单的例子,它表现出同样的问题:

#include <string>

void foo (int&,int){}
void foo (int&,float){}

template <typename T>
void foo (int&& a, T b) {
    foo(a, b);
}

int main()
{
    std::string s;
    typedef decltype(foo(1,s)) foo_type;
}
Run Code Online (Sandbox Code Playgroud)

Live Demo


以下是相关标准报价(N4140):

必须实例化声明,因为涉及重载解析:

[temp.inst]/10: 如果以涉及重载解析的方式使用函数模板或成员函数模板特化,则隐式实例化特化的声明(14.8.3).

只需要声明声明:

[temp.over]/5:只需要函数模板特化的签名即可在一组候选函数中输入特化.因此,仅需要函数模板声明来解析模板特化是候选的调用.

并且不允许实现实例化函数体:

[temp.inst]/11: 实现不应隐式实例化函数模板,变量模板,成员模板,非虚拟成员函数,成员类或不需要实例化的类模板的静态数据成员.