Включить шаблон, только если возвращаемое выражение допустимо - PullRequest
0 голосов
/ 04 июля 2018

Я хочу написать обертку над 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 перегружен, и компилятор не знает, как разрешить перегрузку при оценке шаблон.

Я верю, что эту ситуацию можно исправить с помощью некоторого умного СФИНА, но я не могу найти то, что работает. Я думаю, что мне нужно что-то вроде «включить этот шаблон, только если 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_if и std::is_invocable и std::result_of, но они привели к множеству ошибок, которые я не мог понять, и, вероятно, было бы бессмысленно обобщать здесь все попытки.

Есть ли способ сделать это правильно? Возможно, с C ++ 14, поэтому кодовая база остается более отсталой, но если C ++ 17 необходим, это тоже нормально.

Ответы [ 2 ]

0 голосов
/ 04 июля 2018

std::endl не перегружен. Это шаблон функции, который объявлен так:

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

Причина, по которой он работает непосредственно для std::ostream, заключается в том, что соответствующий operator << (тот, что для потоковых манипуляторов) является обычной функцией-членом (хотя и сгенерированной из шаблона basic_ostream для char). Ожидается конкретный тип манипулятора. С этим типом параметра можно использовать вывод аргументов шаблона, чтобы вывести аргументы правильной endl специализации.

Поскольку вы, похоже, поддерживаете только std::ostream, решение в @ Jarod42's answer - путь.

0 голосов
/ 04 июля 2018

Вы можете добавить перегрузку к типу силы (и поэтому будет выбрана уникальная доступная перегрузка):

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

Демо

...