3

I'm trying to write a single function template instead of a bunch of similar overloads for the insertion operator. The redundant overloaded versions work, but when I try to unite them in a single function template, the compiler complains of ambiguity. For example:


#include <iostream>
#include <list>

class fooBar
{
public:
    fooBar(int iVal): iValue(iVal) {}
    int getValue() {return iValue;}
    
private:
    int iValue;

};

class foo
{
public:
    foo()
    {
        for(int i = 0; i < 64; i++)
            lstFooBars.push_back(fooBar(i));
    }
    std::list<fooBar>& getList()
    {
        return lstFooBars;
    }

private:
    std::list<fooBar> lstFooBars;
    
};

class bar
{
public:
    bar()
    {
        for(int i = 0; i < 64; i++)
            lstFooBars.push_back(fooBar(i));
    }
    std::list<fooBar>& getList()
    {
        return lstFooBars;
    }

private:
    std::list<fooBar> lstFooBars;
    
};

std::ostream& operator<<(std::ostream& osOut, fooBar& fbrFooBar)
{
    osOut << fbrFooBar.getValue();

    return osOut;
}

template <typename T> std::ostream& operator<<(std::ostream& osOut, T& tContainer)
{
    for(fooBar fbrFooBar: tContainer.getList())
        osOut << "[" << fbrFooBar << "] ";

    return osOut;
}

int main()
{
    foo fooFoo;
    bar barBar;

    std::cout << std::endl << fooFoo << std::endl << std::endl;
    std::cout << std::endl << barBar << std::endl << std::endl;

    return 0;
}

...and the compiler tells me that:

test.cpp: In function ‘std::ostream& operator<<(std::ostream&, T&)’:
test.cpp:63:9: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘const char [2]’)
   63 |   osOut << "[" << fbrFooBar << "] ";
      |   ~~~~~ ^~ ~~~
      |   |        |
      |   |        const char [2]
      |   std::ostream {aka std::basic_ostream<char>}

Why does it work when you overload the same function over and over for each case and it doesn't compile like this? What am I missing here?

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108

2 Answers2

2

You've inadvertedly added a possible overload for const char* by making this:

template<typename T>
std::ostream& operator<<(std::ostream& osOut, T& tContainer)

If you narrow it down a bit with SFINAE, it should work.

This overload will only work for types with a getList() member function for example:

template<typename T, typename U = decltype(std::declval<T>().getList())>
std::ostream& operator<<(std::ostream& osOut, T& tContainer)
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
-1

operator<< by default takes chars as argument, not string literals (inside "s) https://www.cplusplus.com/reference/ostream/ostream/operator-free/.

So, in order to make the call in the code you provide not ambiguous, you should try either to use single chars, or std::string:

#include <iostream>
#include <list>
#include <string>

class fooBar
{
public:
    fooBar(int iVal): iValue(iVal) {}
    int getValue() {return iValue;}

private:
    int iValue;

};

class foo
{
public:
    foo()
    {
        for(int i = 0; i < 64; i++)
            lstFooBars.push_back(fooBar(i));
    }
    std::list<fooBar>& getList()
    {
        return lstFooBars;
    }

private:
    std::list<fooBar> lstFooBars;

};

class bar
{
public:
    bar()
    {
        for(int i = 0; i < 64; i++)
            lstFooBars.push_back(fooBar(i));
    }
    std::list<fooBar>& getList()
    {
        return lstFooBars;
    }

private:
    std::list<fooBar> lstFooBars;

};

std::ostream& operator<<(std::ostream& osOut, fooBar& fbrFooBar)
{
    osOut << fbrFooBar.getValue();

    return osOut;
}

template <typename T> std::ostream& operator<<(std::ostream& osOut, T& tContainer)
{
    for(fooBar fbrFooBar: tContainer.getList())
        //osOut << std::string("[") << fbrFooBar << std::string("] ");  // solution 1: use std::string
        osOut << '[' << fbrFooBar << ']' << ' ';                        // solution 2: use single chars

    return osOut;
}

int main()
{
    foo fooFoo;
    bar barBar;

    std::cout << std::endl << fooFoo << std::endl << std::endl;
    std::cout << std::endl << barBar << std::endl << std::endl;

    return 0;
}
Giogre
  • 1,444
  • 7
  • 19
  • 1
    I'm sorry to say, but this is not the answer. Add `std::cout << "Hello world\n";` in the `main` function and the function template will hit you with ambiguity again. – Ted Lyngmo Dec 11 '20 at 19:32
  • @TedLyngmo: I hate to say, but you're right. I once marked this answer for three reasons: it's simple and clear, it solved my specific problem for a short term and it taught me something I didn't know of. But yours is more complete, so I changed my mind and elected it instead from now on. You got it right, this quick fix is a time bomb!... – Capitan Trueno Dec 12 '20 at 08:53
  • 1
    @CapitanTrueno I also liked this at first glance, but then I noticed that it changed the call to the function, not the matching in the template. I'm not even perfectly happy with my own answer since it'll match not only _your_ types with a `getList()` member function. If you include another library which also has a type with a `getList()` member fuinction and an similar overload for `operator<<` you could get in trouble again - but it's a calculated risk and I find it slim. – Ted Lyngmo Dec 12 '20 at 08:59
  • @TedLyngmo `"Hello world\n"` is a string literal. My answer explicitly states to wrap string literals in `std::string` as one way to solve this `operator<<` issue, so I don't see the point of your criticism. – Giogre Dec 12 '20 at 13:45
  • @Lingo The overload is violating the rules (don't know the paragraph) and makes it impossible to use the standard overload for `const char*`. It breaks existing code. I think those are reasons enough to make people who read the answer an idea of what they are getting in to if they use this approach. I could have motivated it better - but I'm not fond of searching the standard to be able to give you the hard facts. – Ted Lyngmo Dec 12 '20 at 14:08
  • @TedLyngmo If I wrap `"Hello world\n"` in a `std::string` inside the `main()`, the way I suggest to do in my answer, the ambiguity won't hit me again. In your first reply to this answer, you argue that this instead would be the case. What you wrote there is simply not true. – Giogre Dec 12 '20 at 14:18
  • I think you miss the point. The overload breaks _existing_ code. Code that follows the standard. It's not allowed. You can't just add a random overload for which there already exist a standard overload and say "_hey, just adapt your code to_ not _use that overload anymore_". – Ted Lyngmo Dec 12 '20 at 14:22
  • 1
    @TedLyngmo Indeed that may be the inner cause of the `operator<<` malfunction, I'll leave it for the OP to figure out, but I feel I should add that if there is anyone missing the point here, that is you: I have already, very patiently, posted two replies to your first criticism and shown it to be groundless... I have yet to hear from you in fact how `"Hello world\n"` is supposed to break the code after having been wrapped in a `std::string`, as I suggest in the answer. – Giogre Dec 12 '20 at 14:36