Templates: Printing out the contents of any STL containers

I recently came across this[1] code snippet which introduced an interesting template declaration:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T, template<class, class...> class X, class... Args>
std::ostream& operator <<(std::ostream& os, const X<T, Args...>& objs)
{
    os << "{";
    bool commoFlag = false;
    for (auto const& obj : objs) 
    {
        os << (commoFlag ? ", " : "") << obj; // Does the work of printing out container objects
        commoFlag = true;
    }
    os << "}";
    return os;
}


I have some questions:
1. What template concepts are being employed on lines 1-2?
2. Is this not portable? The following code compiles in Visual Studio (MVSC using C++14 standard) but not on the online editor [2].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <string>
#include <vector>

using std::ostream;
using std::string;
using std::cout;

template<class T, template<class, class...> class X, class... Args>
ostream& operator <<(ostream& os, const X<T, Args...>& objs)
{
    os << "{";
    bool commoFlag = false;
    for (auto const& obj : objs) 
    {
        os << (commoFlag ? ", " : "") << obj;
        commoFlag = true;
    }
    os << "}";
    return os;
}

struct Foo 
{
    string id; 
    Foo(string id) : id(id) {}
};

ostream& operator <<(ostream& os, const Foo& foo) 
{
    return os << "Foo(" << foo.id << ")";  // error: ambiguous overload for 'operator<<'
}

int main()
{
    cout << Foo("fooz");
    
    string bar = "bar";
    // cout << bar;     // error: ambiguous overload for 'operator<<'
}


cpp.sh's compile output:

 In function 'std::ostream& operator<<(std::ostream&, const Foo&)':
31:25: error: ambiguous overload for 'operator<<' (operand types are 'std::basic_ostream<char>' and 'const string {aka const std::basic_string<char>}')
31:25: note: candidates are:
10:10: note: std::ostream& operator<<(std::ostream&, const X<T, Args ...>&) [with T = char; X = std::basic_string; Args = {std::char_traits<char>, std::allocator<char>}; std::ostream = std::basic_ostream<char>]
29:10: note: std::ostream& operator<<(std::ostream&, const Foo&)
In file included from /usr/include/c++/4.9/iostream:39:0,
                 from 1:
/usr/include/c++/4.9/ostream:602:5: note: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = std::basic_string<char>] <near match>
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^
/usr/include/c++/4.9/ostream:602:5: note:   no known conversion for argument 1 from 'std::basic_ostream<char>' to 'std::basic_ostream<char>&&'
In file included from /usr/include/c++/4.9/string:52:0,
                 from /usr/include/c++/4.9/bits/locale_classes.h:40,
                 from /usr/include/c++/4.9/bits/ios_base.h:41,
                 from /usr/include/c++/4.9/ios:42,
                 from /usr/include/c++/4.9/ostream:38,
                 from /usr/include/c++/4.9/iostream:39,
                 from 1:
/usr/include/c++/4.9/bits/basic_string.h:2772:5: note: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::basic_string<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
     operator<<(basic_ostream<_CharT, _Traits>& __os,
     ^
32:1: warning: control reaches end of non-void function [-Wreturn-type]


The issue is ambiguous definitions for operator<<. Does anyone know why?

Note: 7/29/2022 Stack Overflow is down for maintenance
[1] SO: overloading << operator for C++ stl containers
https://stackoverflow.com/a/70397378/5972766
[2] http://cpp.sh/
Not a complete answer to your question, but cpp.sh has not been updated in a while, so it's not that it's not "portable", it's just outdated.
To finish the answer: your Foo constructor is not marked explicit, meaning that a std::string can be implicitly converted to a Foo.

This makes the following two functions ambiguous:

  • std::ostream & operator << ( std::ostream &, const std::string & );

  • std::ostream & operator << ( std::ostream &, const Foo & );

In other words, is the std::string version correct? Or is the convert-to-a-Foo first version correct?

I haven’t kept up with the latest standards to see how they have changed things to fix that stupidity, but as a general rule any constructor taking a single argument should be marked as explicit unless you know what you are doing.

Hope this helps.
Topic archived. No new replies allowed.