4

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.

Rupesh Yadav
  • 12,096
  • 4
  • 53
  • 70
joke
  • 654
  • 9
  • 11
  • possible dup: http://stackoverflow.com/questions/476272/how-to-properly-overload-the-operator-for-an-ostream – Stephen May 12 '10 at 15:07
  • Why do you want to parametrize the OSTREAM type? Common idiom would be defining operator<< as: `friend std::ostream& operator<<( std::ostream& os, Test& rhs ) { ... }` – David Rodríguez - dribeas May 12 '10 at 15:46
  • @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 May 12 '10 at 15:55
  • 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 May 12 '10 at 17:05
  • Why do you want to split the definition from the declaration at all costs? – Johannes Schaub - litb May 13 '10 at 15:26

5 Answers5

4

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 Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • specifying a second template parameter for operator<<() works. – joke May 12 '10 at 15:22
  • 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. May 12 '10 at 16:12
1

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;
}
Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
a1ex07
  • 36,826
  • 12
  • 90
  • 103
  • normally you would write: template template STREAM& Test::operator<<(STREAM& os, const Test& rhs) { os << rhs.value_; return os; }; But it doesn't work in this case. – joke May 12 '10 at 15:10
  • Even if it would work, the example still misses: Test::operator<<(...) – joke May 12 '10 at 15:13
  • @~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::`. – Luc Touraille May 12 '10 at 15:27
  • @Luc Touraille: But there is a difference between both declarations. The original can only take Test as second parameter. The second one can take everything event std::cin; – joke May 12 '10 at 15:33
  • But besides that, the example doesn't compile as the way it is. – joke May 12 '10 at 15:42
  • Sorry, I just copy-pasted declaration from the question. Surely, it should be "template friend STREAM& operator<<(STREAM& os, const Test& rhs) " – a1ex07 May 12 '10 at 16:28
1

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
  • 51,233
  • 8
  • 91
  • 143
  • 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. – joke May 12 '10 at 15:30
  • 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 May 12 '10 at 15:42
  • Yes it declares/defines are template member function with one template parameter as soon as the surround template class gets instantiated. – joke May 12 '10 at 15:51
0

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
  • 204,818
  • 23
  • 294
  • 489
0

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.

joke
  • 654
  • 9
  • 11
  • 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 May 12 '10 at 17:03
  • 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. – joke May 12 '10 at 17:16
  • Just follow the code... (boost/asio/basic_socket_iostream.hpp: `template <...> class basic_socket_iostream : public boost::base_from_member<...>, public std::basic_iostream`). 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 May 12 '10 at 17:20