Giovanni Mascellani
Giovanni Mascellani

Reputation: 1278

Enable template only if the return expression is valid

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

Answers (2)

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

Jarod42
Jarod42

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

Demo

Upvotes: 5

Related Questions