views:

65

answers:

5

The following example compiles fine but I can't figure out how to separate declaration and definition of operator<<() is this particular case.

Every time I try to split the definition friend is causing trouble and gcc complains the operator<<() definition must take exactly one argument.

#include <iostream>
template <typename T>
class Test {
    public:
        Test(const T& value) : value_(value) {}

        template <typename STREAM>
        friend STREAM& operator<<(STREAM& os, const Test<T>& rhs) {
            os << rhs.value_;
            return os;
        }
    private:
        T value_;
};

int main() {
    std::cout << Test<int>(5) << std::endl;
}

Operator<<() is supposed to have a free first parameter to work with different kind of output streams (std::cout, std::wcout or boost::asio::ip::tcp::iostream). The second parameter should be bound to a specialized version of the surrounding class.

Test<int> x;
some_other_class y;

std::cout << x; // works
boost::asio::ip::tcp::iostream << x; // works

std::cout << y; // doesn't work
boost::asio::ip::tcp::iostream << y; // works

Besides that using a non-member-function isn't equivalent to splitting the definition and declaration because non-member-functions can't access private attributes the the class.

+1  A: 

Shouldn't it be defined outside of the class ?

template <typename T>
class Test 
{  
    ...
    template <typename STREAM>
    friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);
};

template <typename STREAM, typename T> 
STREAM& operator<<(STREAM& os, const Test<T>& rhs) 
{
    os << rhs.value_;
    return os;
}
a1ex07
Have you tried compiling this?
Nikolai N Fetissov
normally you would write: template <typename T> template <typename STREAM> STREAM return os; };But it doesn't work in this case.
Even if it would work, the example still misses:Test<T>::operator<<(...)
@~joke: `operator<<` is not a member function (i.e. method), it is a free function that you can (if needed) declare as friend. Therefore, it must not be prefixed by `Test<T>::`.
Luc Touraille
@Luc Touraille: But there is a difference between both declarations. The original can only take Test<T> as second parameter. The second one can take everything event std::cin;
But besides that, the example doesn't compile as the way it is.
a1ex07
+3  A: 

The easiest is probably to make all these template operators friends:

#include <iostream>
template <typename T>
class Test
{
    public:
        Test(const T& value) : value_(value) {}

        template <typename STREAM, typename U>
        friend STREAM& operator<<(STREAM& os, const Test<U>& rhs);

    private:
        T value_;
};

template <typename STREAM, typename T>
STREAM& operator<<( STREAM& os, const Test<T>& rhs )
{
    os << rhs.value_;
    return os;
}
Nikolai N Fetissov
specifying a second template parameter for operator<<() works.
I could not find a way to reuse `T` either, even the typical template member function of having 2 `template` declaration preceding the function definition. It does not matter in term of accessibility, but does seems strange nonetheless.
Matthieu M.
+1  A: 

The nearest I can achieve is

#include <iostream>

template <typename T>
class Test;

template <typename STREAM, typename T>
STREAM& operator<<(STREAM& os, const Test<T>& rhs);

template <typename T>
class Test {
public:
    Test(const T& value) : value_(value) {}

    template <typename STREAM, typename U>
    friend STREAM& operator<< (STREAM& os, const Test<U>& rhs);

private:
    T value_;
};

template <typename STREAM, typename T>
STREAM& operator<<(STREAM& os, const Test<T>& rhs) {
    os << rhs.value_;
    return os;
}

int main() {
    std::cout << Test<int>(5) << std::endl;
}

which declares all operator<< as friend instead of only the one parametrized by T. The problem is that it isn't possible to partially specialize functions. One would have liked to use

template <typename STREAM>
friend STREAM& operator<< <STREAM, T> (STREAM& os, const Test<T>& rhs);

but that isn't valid syntax. (Well, and partial specialization can't declared friend)

AProgrammer
You're right it's not the same thing as the original. But since the original version can be defined within the class definition there should be a way of splitting declaration and definition.
The original defines a function with one template parameter, the first. If you want something equivalent, you'll have to define one function per type T. It is possible, but probably painful.
AProgrammer
Yes it declares/defines are template member function with one template parameter as soon as the surround template class gets instantiated.
A: 

The problem is that in the code that you present the friend is a templated function parametrized only on the first argument type. That is, for each instantiating type T of the class template (call it mytype), you are declaring a free template function:

template <typename STREAM>
STREAM& operator<<( STREAM& os, Test<mytype> const & x );

The important point there is that Test<mytype> is the particular instantiation of Test with type argument mytype.

If you really want to declare a friend function that is templated in both the stream type and the instantiating type of the Test template, you must declare a friend with two arguments.

On the other hand, I recommend that you do not parametrize operator<< on the stream type, and at the same time, that you define it inside the class braces as it has slight advantages (name lookup rules are slightly different).

David Rodríguez - dribeas
@dribeas: http://stackoverflow.com/questions/2819994/2820743#2820743
A: 

For each instantiated type T of class Test a template function operator<<() is exposed which can operate on different kind of streams. The operator<<() function has a free first parameter but a fixed second parameter.

example:

Test<int> x;
some_other_class y;

std::cout << x; // works
boost::asio::ip::tcp::iostream << x; // works

std::cout << y; // doesn't work
boost::asio::ip::tcp::iostream << y; // works

That's the way the Test class was supposed to work.

Don't `boost::asio::ip::tcp::iostream` and `boost::asio::ip::tcp::iostream` inherit from `std::basic_iostream` that itself inherits from `std::basic_ostream`??
David Rodríguez - dribeas
I don't think they inherit. They should be template specializations.boost::asio::ip::tcp::iostream should be a specialization of the template basic_socket_iostream.
Just follow the code... (boost/asio/basic_socket_iostream.hpp: `template <...> class basic_socket_iostream : public boost::base_from_member<...>, public std::basic_iostream<char>`). It just makes sense, the whole point is that you do not need to rewrite any `operator<<` to use the asio iostreams. If you can write to `cout` then you can write to a tcp stream.
David Rodríguez - dribeas