0

I want to wrap C++ streams in a template class such that all << operations that are defined for the streams are already available for the wrapper class.

Can you change the following code such that it compiles without altering the overall intention too much?

#include <iostream>
class Foo
{
private:

    std::ostream& os;

public:

    explicit Foo( std::ostream& os ) : os( os ) {};

    template<class T>
    Foo& operator<<( const T& t )
    {
        os << t << '!';
        return *this;
    }

};

int main()
{

    Foo( std::cout ) << "test" << '\n'; // works fine 
    Foo( std::cout ) << "test" << std::endl; // compilation error

    return 0;
}

I assumed that std::endl has some type and hence is 'caught' by the templated method.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
shuhalo
  • 5,732
  • 12
  • 43
  • 60
  • [`std::endl`](https://en.cppreference.com/w/cpp/io/manip/endl) is a templated function so its type can't be deduced by itself. Streams have an [overload](https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt) for functions of that form to help in type deduction. – kmdreko Sep 13 '18 at 08:35
  • [This thread](https://stackoverflow.com/q/51871383/9593596) should be helpful. The context is a bit different (variadic parameter packs), but the answers should clarify what the issue is and how to mitigate it. – lubgr Sep 13 '18 at 08:44
  • I need an example that actually compiles. – shuhalo Sep 13 '18 at 09:15

2 Answers2

0

As suggested by kmdreko, you need to add an overload for template functions

(std::endl is a template function, not a class or simple type)

Like this :

#include <iostream>
class Foo
{
private:

    std::ostream& os;

public:

    explicit Foo(std::ostream& os) : os(os) {};

    // for classes
    template<class T>
    Foo& operator<<(const T& t)
    {
        os << t << '!';
        return *this;
    }

    // for endl
    Foo& operator<<(std::ostream& (*pf) (std::ostream&)) {
        os << pf;
        return *this;
    }
};
VB_overflow
  • 1,763
  • 11
  • 15
0

You should probably guard it with SFINAE and perfect-forward the argument.

    template<class T> auto operator<<(T &&t)
         -> decltype(os << std::forward<T>(t) << '!', *this)
    {
            return os << std::forward<T>(t) << '!', *this;
    }

Now this should accept every argument std::ostream can print and fail to compile with everything else.

bipll
  • 11,747
  • 1
  • 18
  • 32