views:

247

answers:

6

I am trying to write a piece of code for fun using C++ templates.

#include <iostream>
#include <vector>

template <class Container>
std::ostream& operator<<(std::ostream& o, const Container& container)
{
    typename Container::const_iterator beg = container.begin();

    o << "["; // 1

    while(beg != container.end())
    {
     o << " " << *beg++; // 2
    }

    o << " ]"; // 3

    return o;
}

int main()
{
    std::vector<int> list;

    list.push_back(0);
    list.push_back(0);

    std::cout << list;

    return 0;
}

The above code doesn't compile :)

At 1, 2, 3 the same error is produced : error C2593: 'operator <<' is ambiguous

All what I am trying to do is overloading the << operator to work with any container. Does that make sense ? How would that be done If possible, if not why ?

EDIT :: Thanks for corrections :) 'sth' way is a good solution.

I am just curious if this ambiguity -as Neil explained- would go away if we could use C++0x Concepts ?

+3  A: 

What is the error? I saw one, you need a typename:

typename Container::iterator beg = container.begin();

What happens here is that the compiler doesn't know anything about Container when it is first reading it. So we have to give it a little help and tell it that iterator will be a type(syntactically it could be any valid name at class scope, so a function, variable,...). When writing a template method, any type that depends on the template type must specify that it is a type with the keyword typename.

Matt Price
I am not sure, but what does typename means ?
AraK
Thanks, I edited the code. Still it does not compile.
AraK
+1  A: 

Ok, now your template it causing confusion. The compiler can't decide between your operator and the one that you would expect.

Matt Price
+2  A: 

Your operator introduces its own ambiguity - it could itself be used for printing the things it is trying to print. My advice:

  • use a named function, not an operatr
  • pass the container by const reference (this is nothing to do with the problem, however)
anon
sorry, I forgot that :)
AraK
+4  A: 

Your newly defined operator<< does not only match containers, but also any other types like ints and strings. That's why the compiler complains about ambiguities when it needs to find the matching operator<< to output "[".

One way to work around this problem would be to rename your output function:

template <class Container>
std::ostream& container_out(std::ostream& o, const Container &container) {
  // ...
}

You can then add simple wrappers to enable operator<< for all the containers you want to print:

template<typename T>
std::ostream& operator<<(std::ostream& o, const std::vector<T> &container) {
  return container_out(o, container);
}

template<typename T>
std::ostream& operator<<(std::ostream& o, const std::map<T> &container) {
  return container_out(o, container);
}
sth
I don;t think those operater<< specializations will work, since they don't take into account the optional allocator template parameter.
Evan Teran
And the name _out is reserved at namespace scope.
anon
@Evan Teran: It will work for vectors that use the default allocator, for others the `operator<<` definition needs to be extended by the additional argument.
sth
@sth: you're right, i stand corrected
Evan Teran
+2  A: 

You can restrict your operator<< to only apply to templated containers by specifying that the Container template parameter is itself templated. Since the C++ std containers also have an allocator template parameter you also have to include this as a template parameter of Container.

template
    < typename T
    , template<typename ELEM, typename ALLOC=std::allocator<ELEM> > class Container
    >
std::ostream& operator<< (std::ostream& o, const Container<T>& container)
{
    typename Container<T>::const_iterator beg = container.begin();

    o << "["; // 1

    while(beg != container.end())
    {
        o << " " << *beg++; // 2
    }

    o << " ]"; // 3

    return o;
}

int main()
{
    std::vector<int> list;

    list.push_back(0);
    list.push_back(0);

    std::cout << list;

    return 0;
}
jon hanson
+1  A: 

Maybe not as generic as what you are posting (you need to pass the type contained), and also leaves an extra separator at the end, but you can use the STL for this:

std::vector<int> v;
v.push_back( 10 );
v.push_back( 20 );

std::cout << "[";
std::copy( v.begin(), v.end(), std::ostream_iterator<int>( std::cout, " " ) );
std::cout << "]";

Will output:

[10 20 ]

Note that the extra separator is at the end of the sequence and that there is no initial space between the [ and the first element.

David Rodríguez - dribeas