C++ 确保参数是控制台的输出流

C++ 确保参数是控制台的输出流,c++,inheritance,c++11,variadic-templates,typetraits,C++,Inheritance,C++11,Variadic Templates,Typetraits,我正在尝试制作一个用于颜色的流操纵器,用于控制台的输出。它可以改变文本和背景的颜色: std::cout << ConColor::Color::FgBlue << 123 << "abc"; //text is blue, sticky 此签名也允许派生类,例如std::ostringstream,但无法更改字符串流的颜色。该函数将更改控制台的颜色,无论是否使用此类参数调用 因此,我希望确保该参数与std::cout、std::wcout等类似。我希望在将

我正在尝试制作一个用于颜色的流操纵器,用于控制台的输出。它可以改变文本和背景的颜色:

std::cout << ConColor::Color::FgBlue << 123 << "abc"; //text is blue, sticky
此签名也允许派生类,例如
std::ostringstream
,但无法更改字符串流的颜色。该函数将更改控制台的颜色,无论是否使用此类参数调用

因此,我希望确保该参数与
std::cout
std::wcout
等类似。我希望在将来的标准中添加更多
std::ostream
对象的情况下,该参数是通用的

我尝试了许多涉及
std::is_same
std::is_base_of
的事情,但前者不起作用,只是为了最终意识到这是毫无意义的,因为从
std::basic_ostream
继承的任何参数类型在传递到函数时都将转换为我比较的类型,给出假阳性

这最终让我找到了下面的答案(可变模板参数?哇,真是太多了!)不过有几个问题:

  • 编译器必须支持可变模板。我更喜欢MSVC上的解决方案工作
  • 如果使用的派生类具有不同数量的模板参数(例如
    std::ostringstream
    ,它有3个而不是2个),则编译器会给出隐藏的错误,因为它没有通过函数签名
  • 例如,可以将stdout重定向到文件,因此即使参数是
    std::cout
    ,也会发生与stringstream相同的情况

我鼓励人们发布任何其他解决方案,希望比我的解决方案更好,并且真的希望能够与至少VS11一起工作。

这是我经过大量试验后得出的结论:

template<template<typename...> class T, typename... U>
void foo(T<U...> &os) {
    static_assert(
        std::is_same<
            std::basic_ostream<U...>, 
            typename std::remove_reference<decltype(os)>::type
        >::value, 
        "Argument must be of type std::basic_ostream<T, U>."
    );
    //...
}
模板
void foo(T&O){
静态断言(
std::是一样的吗<
标准::基本团队,
typename std::remove_reference::type
>::价值,
“参数的类型必须为std::basic_ostream。”
);
//...
}
可以找到包含以下每个测试的源代码。
可以找到用类似的自制类型替换这些类型的源代码,这些类型更加明确,并提供更多的自由度(例如,实例化),这可能对测试更有用

  • 传入
    std::cout
    std::wcout
    可以使它很好地编译
  • 传入std::ostringstream的实例会导致它抱怨模板参数的数量
  • 传入具有相同数量模板参数的
    std::fstream
    实例会导致静态断言失败
  • 传入自制的2参数模板类会导致静态断言失败

请随时以任何方式对此进行改进。

以下是用于检测
std::basic_ostream
实例化的特性:

template<typename T> struct is_basic_ostream {
  template<typename U, typename V>
  static char (&impl(std::basic_ostream<U, V> *))[
    std::is_same<T, std::basic_ostream<U, V>>::value ? 2 : 1];
  static char impl(...);
  static constexpr bool value = sizeof(impl((T *)0)) == 2;
};
template结构是\u basic\u ostream{
模板
静态字符(&impl(std::basic_ostream*))[
std::值是否相同?2:1];
静态字符impl(…);
静态constexpr bool value=sizeof(impl((T*)0))==2;
};
用作:

template<typename T>
void foo(T &) {
  static_assert(is_basic_ostream<T>::value,
    "Argument must be of type std::basic_ostream<T, U>.");
}
模板
void foo(T&){
静态断言(即基本断言):,
“参数的类型必须为std::basic_ostream。”);
}
我们使用模板参数推断来推断(非正确的)
basic_ostream
基类上的模板参数(如果有)。作为一个更通用的解决方案,将
U
V
替换为单个可变参数将允许在支持可变模板参数的编译器上编写一个通用的
is\U实例化
trait



要检测stdout是否通过管道传输到文件(当然,只能在运行时检测到),请使用
isatty
;请参见

是的,没关系,我检查了链接,发现您没有将其用作操纵器。无论如何,非控制台
std::ostream
编译成功:@jrok,嗯,我以为你不能创建
std::ostream
对象,但显然你可以间接地创建,所以我想不是
std::ostream
的所有对象都可以假设与控制台绑定。就我用它的目的而言,它可能很好,但这是一个值得欢迎的改进。好的想法。同时感谢
isatty
提示,非常感谢您删除可变模板。我从未见过使用可变模板参数(如果它们被称为可变模板参数的话),这可能是有原因的。我相信Windows有一种非常简单的方法来检查stdout是否是CONOUT$或类似的东西,如果它跨平台运行,可以与#ifdef结合使用。
template<typename T>
void foo(T &) {
  static_assert(is_basic_ostream<T>::value,
    "Argument must be of type std::basic_ostream<T, U>.");
}