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
  • 15,096
  • 5
  • 54
  • 71
joke
  • 634
  • 9
  • 10

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
  • 79,853
  • 11
  • 109
  • 168
  • specifying a second template parameter for operator< – 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
  • 76,669
  • 15
  • 85
  • 137
a1ex07
  • 36,234
  • 12
  • 86
  • 101
  • normally you would write: template template STREAM& Test::operator<& 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<::`. – 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<& 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
  • 49,582
  • 8
  • 87
  • 140
  • 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
  • 198,982
  • 21
  • 284
  • 478
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
  • 634
  • 9
  • 10
  • 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< – David Rodríguez - dribeas May 12 '10 at 17:20