c++templateslanguage-lawyeroverloadingc++-concepts

Order of c++ template arguments affecting compilation


I just encountered a new compilation error in my code after a visual studio update. I have stripped my code down to what I think is a minimal example. I have two templated functions both called pow - the first takes two parameters of any type and calls std::pow with them. The second uses a concept to override the first template such that if the second parameter matches the concept it gets called instead.

In my code, I for some reason had the two template parameters in the opposite order to how they appeared in the parameter list. This used to compile, but recently stopped compiling with the error 'pow': ambiguous call to overloaded function. However, if I swap the order of the template parameters to match the function parameters it compiles fine.

Why would this compile one way and not the other?

Here is the minimal example

#include <cmath>
template<class T, class U>
double pow(const T& base, const U& power)
{
    return std::pow(base, power);
}

template<class T>
concept HasValue =
    requires(std::remove_cvref_t < T > t)
{
    t.value;
};


//this causes an ambiguous call to overloaded function compilation error
template<HasValue T, class U>
double pow(const U& s, const T& a)
{
    return std::pow(s, a.value);
}

//this works with no compilation error
//template<class U, HasValue T>
//double pow(const U& s, const T& a)
//{
//  return std::pow(s, a.value);
//}

class MyClass
{
public:
    double value;
};

int main()
{
    MyClass myClass;
    myClass.value = 2.0;
    pow(10.0, myClass);
}

Solution

  • The partial ordering of function templates is defined in section 13.7.7.3 of the Standard. Quoting (from N4981) paragraph 2:

    Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process. If both deductions succeed, the partial ordering selects the more constrained template (if one exists) as determined below.

    The next paragraphs go into details of transformations applied to template parameters and how unique type is synthesized.

    Paragraph 6 describes the ordering of transformed templates. The case when you have

    template<class T, class U>
    double pow(const T& base, const U& power);
    template<class U, HasValue T>
    double pow(const U& s, const T& a)
    

    falls through to 6.4

    (6.4) — Otherwise, if one template is more constrained than the other (13.5.5), the more constrained template is more specialized than the other.

    The case when you have a reversed order of template types

    template<class T, class U>
    double pow(const T& base, const U& power);
    template<HasValue T, class U>
    double pow(const U& s, const T& a);
    

    goes to reversed order rules 6.2 and lands in 6.2.2

    (6.2.2) — Otherwise, if the corresponding template-parameters of the template-parameter-lists are not equivalent (13.7.7.2) 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.

    and since neither template is more specialized than the other it results in ambiguity error emitted by compilers.