仅当 return 表达式有效时启用模板

Enable template only if the return expression is valid

我想用这种风格在 std::ostream 上写一个包装器:

#include <iostream>

struct OstreamWrapper {
  OstreamWrapper(std::ostream &out) : out(out) {}

  template< typename T >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

  std::ostream &out;
};

int main() {
  OstreamWrapper wrap(std::cout);
  wrap << "Hello, world!";  // This works
  wrap << std::endl;        // This does not work
  return 0;
}

这种方法的问题在于它不适用于(例如)std::endl,因为(据我所知)std::endl 已重载,编译器不知道如何在评估模板时解决重载问题。

我相信一些聪明的 SFINAE 可以解决这种情况,但我找不到有效的方法。我想我需要类似 "enable this template only when cout << arg is a well formed expression" 的东西,但我不知道如何表达。

例如,我试过这个:

  template< typename T,
            typename = decltype(out << arg) >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

但这不行,因为模板表达式被求值时,arg 还没有定义。

  template< typename T,
            typename = decltype(out << std::declval< T >()) >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

这编译了,但没有做我想要的,因为它需要知道类型 T,而我的问题实际上在于建立如何重载它的参数。

我也尝试过基于std::enable_ifstd::is_invocablestd::result_of的更晦涩的条件,但它们引入了很多我无法理解的错误,这可能毫无意义在这里总结所有的尝试。

有没有办法正确地做这件事?可能与 C++14 一起使用,因此代码库仍然更加向后兼容,但如果需要 C++17,也可以。

您可以添加重载以强制类型(因此选择了唯一可用的重载):

struct OstreamWrapper {
    explicit OstreamWrapper(std::ostream &out) : out(out) {}

    template< typename T >
    decltype(auto) operator<<(T &&arg) {
        return out << std::forward<T>(arg);
    }

    decltype(auto) operator<<(std::ostream& (&arg)(std::ostream&)) {
        return out << arg;
    }

    std::ostream &out;
};

Demo

std::endl 没有超载。这是一个函数模板,声明如下:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

它直接用于 std::ostream 的原因是适当的 operator <<(用于流操纵器的那个)是一个常规成员函数(尽管从 basic_ostream 模板生成 char).它需要一个具体的操纵器类型。模板参数推导可与此参数类型一起使用,以推导正确 endl 专业化的参数。

由于您似乎只支持 std::ostream 中的解决方案是可行的方法。