Is it valid to overload function with extra non-deducible template parameter?

Viewed 190

The following code compiles and works fine with gcc (9), clang (11) and msvc (16.28):

template <class A>
struct X {
    A a;

    constexpr X(A a) : a{a} { }
};

template <class A>
constexpr auto fn(X<A> const& x) {
    return X<A>(x.a - 1);
}


template <class Xs, class A>
constexpr auto fn(X<A> const& x) {
    return Xs(x.a - 1);
}

constexpr X<int> x1{3};
constexpr auto x2 = fn(x1);
constexpr auto x3 = fn<X<double>>(x1);

There are two fn functions with identical declarations except for the extra Xs parameter in the second one.

I'd like to be sure that this is standard-accepted and not something that these compilers provide as extra? Since all 3 do, I'd guess this would be standard, but you never know.

I'd also like to know if my assumptions as to why this work/would be standard are correct:

  • in the call fn(x1), Xs cannot be deduced so the second overload of fn is discarded gently (SFINAE?)?
  • in the call fn<Xs>(x1), the first overload would then be fn(X<X<int>>) which does not match the argument x1, thus the first overload is also discarded?
1 Answers

There are two fn functions with identical declarations except for the extra Xs parameter in the second one.

That's fine, you can overload function templates using different arguments or different template arguments.

  • in the call fn(x1), Xs cannot be deduced so the second overload of fn is discarded gently (SFINAE?)?

Yes, except it's not SFINAE, but overload resolution. The S in SFINAE stands for substitution. When a template argument can't be deduced, no substitution takes place.

It's described in [temp.over]/1:

... For each function template, if the argument deduction and checking succeeds, the template-arguments (deduced and/or explicit) are used to synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution.

With regard to the second bullet (fn<X<double>>(x1)), both templates can synthesize a valid declaration, but X<int> isn't convertible to X<X<double>>, so the second overload is selected as the only viable one.

Note: when multiple template overloads are viable, partial ordering is performed to determine the most specialized template.

Related