Jayhello
Jayhello

Reputation: 6602

C++ print template container error (error: ambiguous overload for 'operator<<') understanding?

I want to write template function which can print container like std::vector, std::list.

Below is my function, just overload <<.

template<typename Container>
std::ostream& operator<<(std::ostream& out, const Container& c){
    for(auto item:c){
        out<<item;
    }
    return out;
}

Test code as below:

int main(){
    std::vector<int> iVec{5, 9, 1, 4, 6};
    std::cout<<iVec<<std::endl;
    return 0;
}

ouput:

59146

And I want to add a space string in each value(output like 5 9 1 4 6), so I change the function to:

template<typename Container>
std::ostream& operator<<(std::ostream& out, const Container& c){
    for(auto item:c){
        out<<item<<" ";
    }
    return out;
}

Then it get error:

merror: ambiguous overload for 'operator<<' (operand types are 'std::basic_ostream<char>' and 'const char [2]')
         out<<item<<" ";

I know << can output common type like.

int a = 0;
double f = 0.3;
std::string s = "123";
std::cout<<a<<f<<s<<std::endl;

So Why get the above error ? And is there any way to solve it?

I have see this question Ambiguous overload for ‘operator<<’ in ‘std::cout << but I still can't understand clearly.

All code:

#include <iostream>
#include <vector>

template<typename Container>
std::ostream& operator<<(std::ostream& out, const Container& c){
    for(auto item:c){
        out<<item;
        // out<<item<<" "; // error
    }
    return out;
}

int main(){
    std::vector<int> iVec{5, 9, 1, 4, 6};
    std::cout<<iVec<<std::endl;
    return 0;
}

Upvotes: 2

Views: 488

Answers (4)

Tyker
Tyker

Reputation: 3047

@miradham explained the issue very well.

but this a more generic solution using SFINAE to make the overload considered only for types on which the range-based for loop can be used, what ever their template arguments can be.

type coming form std::basic_string were ignored to prevent ambiguity with the standard operator << to display strings

c-style array will not be displayed using this overload even though they could because they are decayed to pointers and displayed with the standard operator <<

#include <iostream>
#include <vector>
#include <type_traits>
#include <array>
#include <string>

template<template<typename...> typename From, typename T>
struct is_from : std::false_type {};

template<template<typename...> typename From, typename ... Ts>
struct is_from<From, From<Ts...> > : std::true_type {};

template <typename...>
using void_t = void;

template <typename T, typename = void>
struct is_input_iterator : std::false_type { };

template <typename T>
struct is_input_iterator<T,
    void_t<decltype(++std::declval<T&>()),
           decltype(*std::declval<T&>()),
           decltype(std::declval<T&>() == std::declval<T&>())>>
    : std::true_type { };

template<typename Container, 
typename std::enable_if<is_input_iterator<decltype(std::begin(std::declval<Container>()))>::value &&
                        is_input_iterator<decltype(std::end(std::declval<Container>()))>::value &&
                        !is_from<std::basic_string, Container>::value, int>::type = 0>
std::ostream& operator<<(std::ostream& out, const Container& c){
    for(const auto& item:c){
        out << item << " ";
    }
    return out;
}

int main(){

    std::array<int, 6> arr{0, 1, 2, 3, 4, 5};
    std::vector<int> vec{5, 9, 1, 4, 6};

    std::cout << vec << std::endl;
    std::cout << arr << std::endl;
    std::cout << std::string("test") << std::endl;
    return 0;
}

Upvotes: 4

Dev Null
Dev Null

Reputation: 4997

The problem is that operator<< that you defined matches for both std::vector and const char (&array)[N] (the type of " " that you try to stream to out).

A simplified code example that demonstrates the problem:

#include <iostream>

template<typename Container>
std::ostream& operator<<(std::ostream& out, const Container& c)
{
    return out;
}

int main()
{
    std::cout<<" "<<std::endl;
    return 0;
}

The following example would restrict operator<< to std::vectors only:

template<typename ... Args>
std::ostream& operator<<(std::ostream& out, const std::vector<Args...>& c)
{
    for(auto item:c){
        out<<item<<" ";
    }
    return out;
}

Live example.

Upvotes: 1

xaxxon
xaxxon

Reputation: 19771

The problem is that the templated type Container can match any type, not just containers. That includes the " " you are trying to print.

If you look at the error message from a different compiler: https://godbolt.org/g/3YKtca

<source>:5:15: note: candidate function [with Container = char [2]]

std::ostream& operator<<(std::ostream& out, const Container& c){

Perhaps you want a partial specialization of vector<T> to only take vectors. Determining if a type is a container is a more complicated problem.

#include <iostream>
#include <vector>

template<typename E, typename A>
std::ostream& operator<<(std::ostream& out, const std::vector<E, A>& c){
    for(auto item:c){
        out<<item;
        out<<item<<" "; // error
    }
    return out;
}

int main(){
    std::vector<int> iVec{5, 9, 1, 4, 6};
    std::cout<<iVec<<std::endl;
    return 0;
}

https://godbolt.org/g/NJNwmN

Upvotes: 3

miradham
miradham

Reputation: 2355

Declaring template<typename Container> could be dangerous as this template includes 'all' variable types int, char etc. Due to this compiler does not know which operator<< to use.

In order to take only container type variables use template of templates. Here is working code for you

template<typename T, template <typename, typename> class Container>
std::ostream& operator<<(std::ostream& out, const Container<T, std::allocator<T>>& c) {
    for (auto item : c) {
        out << item << " ";
    } 
    return out;
}

int main()
{
    cout << "Hello world" << endl;
    int arr[] = { 0,3,6,7 };
    vector<int> v(arr, arr+4);
    cout << v << endl;
    return 0;
}

Upvotes: 5

Related Questions