Question

When trying out clang-3.4 (compiled from git), it failed to compile one of my projects complaining about ambiguity while resolving overloaded operators. I turned out that there were two templated operators, one of which was declared as a member function, other as a non-member one, which both seems equally good match.

Following SSCCE demonstrates the situation:

#include <iostream>

struct ostr {
        std::ostream& s;

        template<class T>
        ostr& operator<<(const T& x) { s << x; return *this; }
};

struct xy {
        double x, y;
};

template<class Stream>
Stream& operator<<(Stream& s, const xy& x) {
        s << "[" << x.x << ", " << x.y << "]";
        return s;
}

int main() {
        ostr os{std::cout};
        xy x{4, 5};
        os << "Value is: " << x <<"\n";
}

The project compiled fine before and I checked again this SSCCE with several compilers (gcc 4.5, 4.6, 4.7, 4.8 and clang 3.3) and all of them compiled it without any warning (with -Wall -Wextra -pedantic). All compilers were set to C++11/C++0x standard. After adding ctor to ostr, it compiled fine even on MSVC 2012 and 2010)

Making both operator<<s non-member exhibits the ambiguity in all compilers (as expected)

After looking through the standard drafts (N3242 and N3690) I failed to find anything making member functions/operators better match than non-member ones.

So I failed to prove clang-3.4 is wrong and I wonder who's right. Thus my question is:

  • Is this code valid? Should member operators/functions be better match than non-member ones and it's a bug in clang-3.4?
  • Or are all the other compilers wrong/too permissive?

I am aware that changing the second operator<< to non-templated function (with std::ostream instead of template parameter) would resolve the the ambiguity and work as expected, but that's not the point here.

Était-ce utile?

La solution

Overload resolution adds an additional parameter to a member function just for the purpose of overload resolution:

[over.match.funcs]/2

The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list. So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called.

/4

For non-static member functions, the type of the implicit object parameter is

— “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

— “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

Some special rules follow, for example to allow binding an rvalue to this implicit object parameter (for calling member functions w/o ref-qualifier on rvalues, e.g. ostr{std::cout}<<"hello").


The function signatures including the implicit object parameter we need to compare for overload resolution are:

template<class T>
ostr& ostr::operator<<(ostr&, const T&);    // F1

template<class Stream>
Stream& ::operator<<(Stream&, const xy&);    // F2

After substitution for os << x, we get the same signature:

ostr& ostr::operator<<(ostr&, const xy&);
ostr& ::    operator<<(ostr&, const xy&);

So only one of the "tie-breakers" in [over.match.best]/1 could resolve the ambiguity. Indeed, one could apply, namely the "F1 is more specialized than F2" (or vice versa): partial ordering of function templates.

N.B. The procedure of adding an implicit object parameter is specified again in the description of partial ordering [temp.func.order]/3.


For partial ordering of F1 and F2 (as defined above), we first create two unique types:

struct unique_T {};
struct unique_Stream {};

Then we transform F1 into F1' by replacing the template parameter T with the unique type unique_T (and similarly for F2):

ostr& ostr::operator<<(ostr&, const unique_T&);
ostr& ::    operator<<(unique_Stream&, const xy&);

The parameters of the transformed function F1' are now used to try to deduce the template parameters of the untransformed F2:

ostr a0;
unique_T a1; // no reference, no cv-qualifier
::operator<<(a0, a1) // does template argument deduction succeed?

// reminder: signature of ::operator<<
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);

The deduction succeeds for a0 [with Stream = ostr], therefore the type ostr& from F1 is considered to be at least as specialized as type of the corresponding first parameter of F2 (Stream&, with Stream being a template parameter). I'm not sure what happens to the second argument a1, since no deduction takes place for the second parameter of ::operator<< (it is of type const xy&).

Now we repeat the process with arguments from F2' and try to deduce the template parameters of F1:

unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);

// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);

Here, no deduction occurs for the first argument, but it occurs and succeeds for the second argument [with T = xy].

I conclude no function template is more specialized. Therefore overload resolution should fail due to ambiguity.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top