C++ 表达式 SFINAE 和 ostream 操纵器

fun*_*run 5 c++ templates sfinae c++17

我正在尝试学习如何使用 SFINAE。出于练习目的,我试图制作一个std::ostream包装器以制作自定义格式化程序。

这是我的 SFINAE 和自定义输出类。

// Tester
template <class O>
struct is_ostreamable {
    template <class T>
    static auto check(T t) -> decltype(std::declval<std::ostream &>() << t, std::true_type());
    template <class>
    static auto check(...) -> std::false_type;

  public:
    static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};

// Custom class
struct CustomOutput {
    // Constructor etc...
    CustomOutput(std::ostream &os = std::cout) : os{os} {}
    std::ostream &os;

    // Problematic template function
    template <class O, class = std::enable_if_t<is_ostreamable<O>::value>>
    CustomOutput &operator<<(O o) {
        os << o;
        return *this;
    }
};
Run Code Online (Sandbox Code Playgroud)

完全不启用模板structclass无法通过operator<<. 但是,有了这个 SFINAE,ostream 操纵器就不起作用了……我不知道为什么。

错误,以及我的期望:

int main(void){
    CustomOutput{} << "hi"; // Fine

    std::vector<int> vec;
    // CustomOutput{} << vec; // Error. Expected

    CustomOutput{} << std::endl; // Error. WHY?
}
Run Code Online (Sandbox Code Playgroud)

也许我错过了什么?任何帮助将不胜感激。

L. *_* F. 3

首先,调整你的ostreamable班级。目前,您的类需要T可从0. 许多班级的情况并非如此。它应该用来std::declval创造价值:

template <class O>
struct is_ostreamable {
    template <class T>
    static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
    template <class>
    static auto check(...) -> std::false_type;

public:
    static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
Run Code Online (Sandbox Code Playgroud)

这里做了两处修改:

  • 操作数decltype用于std::declval<T>()创建类型 的对象Tstd::declval<T>是一个(故意未定义的)函数模板,T在未计算的操作数(例如 to decltype、 or sizeofnoexcept运算符等)中使用时生成类型的对象,而不依赖于特定的构造签名(在您的情况下是复制构造0)。

  • 参数 tocheck替换为intvalue变量的初始化程序check使用参数进行调用0,因此该int参数确保(int)排名高于(...)重载决策中的排名,以便true_type在可能的情况下选择重载。


std::endl您需要为函数式操纵器( 、等)提供特殊的重载std::flush

using manip = std::ostream& (*)(std::ostream&);

CustomOutput& operator<<(manip m) {
    os << m;
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,没有办法让通用模板版本支持此功能。这是因为std::endl是一个函数模板:

template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);
Run Code Online (Sandbox Code Playgroud)

对于要使用的函数模板,必须确定适当的模板参数。不可能将类型模板参数推导T为通用模板。

不管怎样,这可能是您需要的唯一特殊重载。