首页 > 解决方案 > 仅当返回表达式有效时才启用模板

问题描述

我想用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_ifandstd::is_invocable和的更晦涩的条件std::result_of,但是它们引入了很多我无法理解的错误,在这里总结所有尝试可能毫无意义。

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

标签: c++templatessfinae

解决方案


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

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;
};

演示


推荐阅读