Home > OS >  Ambiguous constructor overload on gcc, but not on clang
Ambiguous constructor overload on gcc, but not on clang

Time:03-31

Let's say we have the following simple code:

#include <iostream>
#include <type_traits>

struct S {
    template <typename T> explicit S(T) noexcept requires std::is_signed<T>::value {
        std::cout << "T\n";
    }
    template<typename T> explicit S(const T&) noexcept {
        std::cout << "const T&\n";
    }
};

int main() {
    S s(4);
}

This code compiles with clang and prints 'T', but with gcc we have the following error:

error: call of overloaded 'S(int)' is ambiguous

My question is what compiler has a bug, gcc or clang?

CodePudding user response:

GCC is correct. It's ambiguous.

First, we have to look at the implicit conversion sequences. In both cases, the identity conversion sequence is involved: int to int, and int to const int& (the latter is considered the identity conversion sequence thanks to [over.ics.ref]/1).

Second, we look at the tie-breaker rules regarding standard conversion sequences, [over.ics.ref]/3.2. None of these tie-breakers apply in this situation. This means that neither implicit conversion sequence is better than the other.

We next have to go to the global tie-breakers. These can allow one overload to be considered better than another even when all implicit conversion sequences for one overload are neither better nor worse than the corresponding implicit conversion sequences for the other. The global tie-breakers are defined in [over.match.best.general]/2. According to the fifth bullet point (and none of the others could possibly apply to this situation), one of the overloads could be better than the other if both are template specializations but one template is more specialized than the other.

To determine whether this is the case, we refer to [temp.func.order], which refers to [temp.deduct.partial]. We are in the context of a function call, so according to (3.1), "the types used are those function parameter types for which the function call has arguments." Then, paragraph 5 strips references, and paragraph 7 strips top-level cv-qualifiers. The result of this is that deduction succeeds in both directions. (That is, even though not every T is a const U&, the deduction succeeds in this direction anyway because const U& gets replaced by U before the actual deduction occurs.)

Going back to [temp.func.order], since deduction succeeds in both directions, the final tie-breaker mentioned in paragraph 2 is whether one template is more constrained than the other. For that, we scroll down to paragraph 6. The bullet point that applies is (6.2.2), according to which:

Otherwise, if the corresponding template-parameters of the template-parameter-lists are not equivalent ([temp.over.link]) or if the function parameters that positionally correspond between the two templates are not of the same type, neither template is more specialized than the other.

Note that in this case, the stripping of references and cv-qualifiers doesn't apply, because that is only done as part of deduction, and we're not doing deduction anymore, so the function parameters types that positionally correspond are T and const T&, which are not the same. Therefore, neither template is more specialized than the other, meaning that the final tie-breaker has failed to prefer one overload over the other.

  • Related