Убедитесь, что аргумент является выходным потоком для консоли

Я пытаюсь сделать манипулятор потока для цвета для использования с выводом на консоль. Работает, меняя цвет текста и фона:

std::cout << ConColor::Color::FgBlue << 123 << "abc"; //text is blue, sticky

Проблема с подписью:

std::ostream &FgBlue(std::ostream &);

Эта сигнатура также допускает производные классы, такие как 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.


person chris    schedule 01.11.2012    source источник


Ответы (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<typename T>
void foo(T &) {
  static_assert(is_basic_ostream<T>::value,
    "Argument must be of type std::basic_ostream<T, U>.");
}

Мы используем вывод аргумента шаблона, чтобы вывести параметры шаблона в (неправильном) базовом классе basic_ostream, если таковые имеются. В качестве более общего решения замена U и V одним вариативным параметром позволит написать общий трейт is_instantiation_of для компиляторов, поддерживающих вариативные параметры шаблона.


Чтобы определить, передается ли stdout в файл (что, конечно, можно обнаружить только во время выполнения), используйте isatty; см. как использовать isatty() для cout, или я могу предположить, что cout == файловый дескриптор 1?

person ecatmur    schedule 01.11.2012
comment
Хорошая мысль. Также спасибо за совет isatty и большое спасибо за удаление вариативных шаблонов. Я ни разу не видел использования вариативных аргументов шаблона шаблона (если они так называются), и, вероятно, не зря. Я уверен, что есть довольно простой способ Windows проверить, является ли stdout CONOUT$ или что-то в этом роде, который можно использовать в сочетании с #ifdef, если он станет кросс-платформенным. - person chris; 01.11.2012

Вот что у меня получилось после долгих проб:

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>."
    );
    //...
}

Исходный код, содержащий каждый из приведенных ниже тестов, можно найти здесь.
Исходный код заменяет типы на аналогичные самодельные, которые являются более явными и предлагают больше свободы (например, создание экземпляров), которые могут быть более полезными для тестирования, можно найти здесь.

  • Передача std::cout и std::wcout делает его компилируемым.
  • Передача экземпляра std::ostringstream заставляет его жаловаться на количество аргументов шаблона.
  • Передача экземпляра std::fstream с таким же количеством параметров шаблона приводит к сбою статического утверждения.
  • Передача самодельного класса шаблона с двумя параметрами приводит к сбою статического утверждения.

Пожалуйста, не стесняйтесь улучшать это любым возможным способом.

person chris    schedule 01.11.2012
comment
Да неважно, я проверил ссылки и увидел, что вы не используете его как манипулятор. В любом случае, компиляция выполняется без консоли std::ostream: LWS - person jrok; 01.11.2012
comment
@jrok, Ха, я думал, что вы не можете создавать объекты std::ostream, но, видимо, вы можете косвенно, поэтому я думаю, что не все объекты std::ostream можно считать связанными с консолью. Для того, для чего я бы его использовал, это, вероятно, хорошо, но это долгожданное улучшение. - person chris; 01.11.2012