简体   繁体   中英

How could I print the contents of any container in a generic way?

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 ?

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

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);
}

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 .

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)

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

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.

http://blog.csdn.net/cqdjyy01234/article/details/19234329 may be a good solution. It works for STL containers and the C-style array.

You can use constexpr to check Datatype of containers like Stacks,Queues for different printing mechanism and add generic method called PrintContainer check the below code.

First check datatype for Stack,Queue,Map etc.

// Template for Checking Map.
template <class T>
struct is_map
{
    static constexpr bool value = false;
};

template <class Key, class Value>
struct is_map<std::map<Key, Value>>
{
    static constexpr bool value = true;
};

// Template for Checking Stack.
template <class T>
struct is_stack
{
    static constexpr bool value = false;
};

template <class T>
struct is_stack<std::stack<T>>
{
    static constexpr bool value = true;
};

// Template for Checking Queue.
template <class T>
struct is_queue
{
    static constexpr bool value = false;
};

template <class T>
struct is_queue<std::queue<T>>
{
    static constexpr bool value = true;
};

Then include General method for Printing Container like this.

template <typename T>
void PrintContainer(T container, bool showSize = false)
{
    if (showSize)
        std::cout << "Size: " << container.size() << std::endl;
    // Container for Map.
    if constexpr (is_map<T>::value)
    {
        for (const auto &[k, v] : container)
        {
            std::cout << k << "\t" << v << std::endl;
        }
    }
    // Container for Stack & Queue.
    else if constexpr (is_stack<T>::value || is_queue<T>::value)
    {
        while (!container.empty())
        {
            if constexpr (is_stack<T>::value)
                std::cout << container.top() << "\t";
            else if constexpr (is_queue<T>::value)
                std::cout << container.front() << "\t";
            container.pop();
        }
    }
    // General Container like list,set etc.
    else
    {
        for (const auto &data : container)
        {
            if constexpr (!is_map<T>::value)
                std::cout << data << "\t";
        }
    }
    std::cout << std::endl;
}

Then call it from Main method like this.

int main()
{
    //Defining containers.
    std::list<int> iList({10, 20, 30, 40, 50});
    std::map<string, string> sMap{
        {"Abdul", "Java"},
        {"Mohammed", "C++"},
        {"Ahmet", "Python"}};
    std::set<int> iSet({10, 20, 30, 40, 50, 10, 30});
    std::array<int, 5> iArray({11, 22, 33, 44, 55});
    std::stack<float> fStack({1.5, 2.5, 3.5, 4.5});
    std::queue<float> fQueue({11.5, 22.5, 33.5, 44.5});

    //Printing data of containers.
    PrintContainer(iList);
    PrintContainer(sMap);
    PrintContainer(iSet);
    PrintContainer(iArray);
    PrintContainer(fStack);
    PrintContainer(fQueue);

    return 0;
}

Output:

10      20      30      40      50
Abdul   Java
Ahmet   Python
Mohammed        C++

10      20      30      40      50
11      22      33      44      55
4.5     3.5     2.5     1.5
11.5    22.5    33.5    44.5

Full Source Code - PrintContainer.cpp

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM