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