仅在返回表达式有效的情况下启用模板

时间:2018-07-04 09:59:35

标签: c++ templates sfinae

我想以这种样式在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来解决这种情况,但是我找不到可行的方法。我想我需要类似“仅在cout << arg是格式正确的表达式时才启用此模板”之类的东西,但是我不知道如何表达它。

例如,我尝试过:

  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,也可以。

2 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:1)

std::endl未超载。是这样声明的功能模板:

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

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

由于您似乎仅支持char,因此@Jarod42's answer中的解决方案是可行的方法。