4
votes

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.

5
Why do you want to parametrize the OSTREAM type? Common idiom would be defining operator<< as: friend std::ostream& operator<<( std::ostream& os, Test<T>& rhs ) { ... }David Rodríguez - dribeas
@dribeas: because I want to be able to use other output streams as well. Like boost::asio::ip::tcp::iostream and boost::asio::local::stream_protocol::iostream.joke
I have added a comment to your 'answer' that is not really an answer but an extension of the question (consider editing the question to add more information, as it is easier to read all the information in one place)David Rodríguez - dribeas
@dribeas: done (thanks for the hint)joke

5 Answers

4
votes

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

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)

1
votes

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

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).

0
votes

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.