1

So, I have a question in regards to an error I recently experienced when I tried to overload the << operator.

I have a file, called "structPixels.h" where in I define a struct as follows:

#pragma once
#include <iostream>

namespace Eng
{
    /**
     * A structure to represent pixels
     */
    typedef struct Pixel{
        unsigned int x; ///The x-coordinate
        unsigned int y; ///The y-coordinate

        bool operator ==(const Pixel& rhs) const
        {
            return (this->x == rhs.x && this->y == rhs.y);
        };

        bool operator !=(const Pixel& rhs) const
        {
            return (this->x != rhs.x || this->y != rhs.y);
        };

        bool operator <(const Pixel& rhs) const
        {
            return std::tie(this->x, this->y) < std::tie(rhs.x, rhs.y);
        }

        friend std::ostream& operator << (std::ostream& out, const Pixel& p);
    } Pixel;
}//End of namespace Eng

namespace std {
    //Removing the inline does not fix the error. Rather, it fixed another error
    //which I had with duplicate symbols
    inline ostream& operator<<(ostream &os, const Eng::Pixel &p)
    {
        os << "{" 
        << p.x 
        << "," 
        << p.y 
        << "}";
        return os;
    }
}//End of namespace std

However, when I create it and call a cout like so:

#include "structPixels.h"

Pixel test = {3, 4};
std::cout << "Test: " << test << std::endl;

I get that:

error: use of overloaded operator '<<' is ambiguous (with operand types 'basic_ostream<char, std::__1::char_traits<char> >' and 'Eng::Pixel')

std::cout << "Test: " << test << std::endl;
~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~

EDIT: From the help below, we moved the operator, such that the code looks like so:


namespace Eng
{

    /**
     * A structure to represent pixels
     */
    typedef struct Pixel{
        unsigned int x; ///The x-coordinate
        unsigned int y; ///The y-coordinate

        bool operator ==(const Pixel& rhs) const
        {
            return (this->x == rhs.x && this->y == rhs.y);
        };

        bool operator !=(const Pixel& rhs) const
        {
            return (this->x != rhs.x || this->y != rhs.y);
        };

        bool operator <(const Pixel& rhs) const
        {
            return std::tie(this->x, this->y) < std::tie(rhs.x, rhs.y);
        }
    } Pixel;

    inline std::ostream& operator<<(std::ostream& os, const Pixel& rhs)
    {
        os << "{" << rhs.x << "," << rhs.y << "}";
        return os;
    }
}//End of namespace Eng

This solved the error :)!

Tikki
  • 179
  • 1
  • 10

2 Answers2

8

First, you cannot add function overloads into std. Doing so is undefined behavior. What you want to do is put

inline std::ostream& operator<<(std::ostream &os, const Eng::Pixel &p)
{
    os << "{" 
    << p.x 
    << "," 
    << p.y 
    << "}";
    return os;
}

inside the Eng namespace. Doing that allows the code to compile just fine. Having the operator in the namespace also allows for ADL to work so the operator will be found even if the user doesn't have using namespace Eng; in their code.


The reason you get am ambiguity from doing what you did is because the compiler finds two different functions. It finds

friend std::ostream& operator << (std::ostream& out, const Pixel& p);

via ADL, and it finds the function you defined in std. Since those functions are in different namespaces they are considered as overloads and since both are equal, you get an ambiguity error.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Doing as explained, it first complains about 3 parameters, which is quickly resolved by removing Pixel p and replacing p with this. However, this introduces a new error: error: invalid operands to binary expression ('basic_ostream >' and 'Eng::Pixel') – Tikki Nov 20 '19 at 21:45
  • 1
    @Tikki Works fine: https://godbolt.org/z/apGXQa Did you check the link Nathan included? – Ted Lyngmo Nov 20 '19 at 21:49
  • @Tikki Not sure why, I'd have to see an [mre] in order to tell you. The solution works just fine [here](http://coliru.stacked-crooked.com/a/36e6d00758a6d2cb) – NathanOliver Nov 20 '19 at 21:49
  • 1
    I realised a mistake I just made - I acidently put it within the struct of eng. I moved it out of the struct and into the namespace and it worked. Thank you! – Tikki Nov 20 '19 at 21:49
  • @Tikki No problem. Glad to help. – NathanOliver Nov 20 '19 at 21:50
  • @NathanOliver-ReinstateMonica Another side question, although the points have already been awarded; if I remove inline, I get a duplicate symbol error. Do you know why this is such? The file.h is included in two other files: duplicate symbol 'Eng::operator<<(std::__1::basic_ostream >&, Eng::Pixel const&)' in: CMakeFiles/Main.dir/main.cpp.o CMakeFiles/Main.dir/classHPAMaze.cpp.o – Tikki Nov 20 '19 at 21:59
  • 1
    @Tikki The way C++ works is if you have something defined in header file like a variable or a function, then that definition is copied into every cpp file that includes it. That means when the linker gets to the code, it sees a bunch of translation units that define the same thing. The linker can't handle that and you get an error. When you use `inline` you say *hey, I know this was defined in other translation units but that is okay. Just toss out all the repeats and just keep one defintion*. – NathanOliver Nov 20 '19 at 22:02
  • 1
    While it’s definitely not a good idea, it’s okay to add a function to `std` **if** one or more of its arguments is a user-defined type. – Pete Becker Nov 20 '19 at 22:05
  • I could marry you all. Thanks again :) – Tikki Nov 20 '19 at 22:06
  • @PeteBecker No it is not. [You are explicitly not allowed to do that](https://timsong-cpp.github.io/cppwp/requirements#namespace.std-7). Only template specializations can be added. – NathanOliver Nov 20 '19 at 22:09
  • @NathanOliver-ReinstateMonica -- The current standard says "A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited." [namespace.std]/1. `operator<<` is a template function, so specializing it in `std` is okay. C++20 (i.e., not the standard), which you link to, has essentially the same language in paragraph 2. Paragraph 7, which you linked to, is about **customization points**. – Pete Becker Nov 22 '19 at 15:04
  • @PeteBecker A template specialization has the form `template <> return_type function_name(function_params)`. The OP added `return_type function_name(function_params)` which is an overload, not a specialization. You can't add overloads, only specializations. – NathanOliver Nov 22 '19 at 15:07
  • @NathanOliver-ReinstateMonica -- that paragraph 7 that you linked to is irrelevant, since it talks about what's **allowed outside of** `std`. The operative language is in paragraph 1, "Unless otherwise specified, the behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std." – Pete Becker Nov 22 '19 at 15:16
  • @PeteBecker It states *Other than in namespace std or in a namespace within namespace std, a program may provide an overload* Which means in namespace `std` or a namespace within it is not allowed to add an overload. Google: can we add overloads into std namespace and you'll see every answer says you can't. – NathanOliver Nov 22 '19 at 15:19
  • @NathanOliver-ReinstateMonica -- no, it means that **that paragraph** says that it's okay to write these things outside of `std`. It says nothing about what's allowed in `std`. But, really, why do you insist that this is the operative paragraph, when paragraph 1 **clearly says what you want**? Take a victory when you get it. – Pete Becker Nov 22 '19 at 15:35
2

The problem is that you've declared two overloads of operator<< -- one in namespace Eng (the friend declaration in the class) and one in namespace std. The compiler doesn't know which to use.

Generally you should put operator<< overloads (only) in the namespace of the class being output. You should never put anything into namespace std, as it is undefined as to whether it will work.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226