Reputation: 1278
I want to write a wrapper over std::ostream
in this style:
#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;
}
The problem with this approach is that it does not work (for example) with std::endl
, because (as I get it) std::endl
is overloaded, and the compiler does not know how to resolve the overload when it evaluates the template.
I believe that this situation can be fixed with some clever SFINAE, but I cannot find something that works. I think I need something like "enable this template only when cout << arg
is a well formed expression", but I do not know how to express that.
For example, I tried this:
template< typename T,
typename = decltype(out << arg) >
decltype(auto) operator<<(T &&arg) {
return out << std::move< T >(arg);
}
But this is not ok, because then the template expressions are evaluated, arg is not yet defined.
template< typename T,
typename = decltype(out << std::declval< T >()) >
decltype(auto) operator<<(T &&arg) {
return out << std::move< T >(arg);
}
This compiles, but does not do what I want, because it requires the type T
to be known, while my problem actually lies in establishing how to overload its parameter.
I have also tried more obscure conditions based on std::enable_if
and std::is_invocable
and std::result_of
, but they introduced a lot of errors I could not understand and it probably would be pointless to summary here all the attempts.
Is there a way to do this thing properly? Possibly with C++14, so the codebase remains more backward compatbile, but if C++17 is necessary it is ok as well.
Upvotes: 1
Views: 307
Reputation: 170065
std::endl
is not overloaded. It's a function template that's declared like this:
template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );
The reason it works directly for std::ostream
is that the appropriate operator <<
(the one for stream manipulators) is a regular member function (though generated from the basic_ostream
template for char
). It expects a concrete manipulator type. Template argument deduction can be used with this parameter type to deduce the arguments of the correct endl
specialization.
Since you seem to support only std::ostream
, the solution in @Jarod42's answer is the way to go.
Upvotes: 1
Reputation: 217145
You might add overload to force type (and so unique available overload is chosen):
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;
};
Upvotes: 5