带额外不可推导模板参数的函数重载是否符合C++标准?
Great question! First off: your code is 100% compliant with the C++ standard—this isn't a compiler-specific extension. Now let's verify your assumptions and walk through the standard rules that govern this behavior:
1. What happens when calling fn(x1)?
Your first assumption is spot-on: the second fn overload is quietly discarded, and this is an example of SFINAE (Substitution Failure Is Not An Error).
Here's why:
When resolving overloads, the compiler tries to deduce template parameters for each candidate. For the second overload:
template <class Xs, class A> constexpr auto fn(X<A> const& x) { return Xs(x.a - 1); }
You only pass the argument x1 (type X<int>), with no explicit template argument for Xs. Since Xs only appears in the return type (which isn't a valid context for template parameter deduction), the compiler can't figure out what Xs should be.
Per the C++ standard, this deduction failure doesn't trigger a compile error—SFINAE dictates we just drop this overload from the candidate list. That leaves only the first overload:
template <class A> constexpr auto fn(X<A> const& x) { return X<A>(x.a - 1); }
Here, A is easily deduced as int from x1's type X<int>, so it instantiates to fn(X<int> const&) and returns X<int>{2}.
2. What happens when calling fn<X<double>>(x1)?
Your core idea here is correct, but let's clarify a small detail about why the first overload is discarded:
When you explicitly specify X<double> as a template argument, we check each overload:
- For the first
fnoverload (which has one template parameterA),X<double>gets bound toA. This instantiates the function tofn(X<X<double>> const&)—but your argumentx1isX<int>, which doesn't matchX<X<double>>at all. This overload is rejected because the function signature doesn't match the argument type, not because of SFINAE. - For the second
fnoverload (which has two template parametersXsandA),X<double>is bound toXs, andAis deduced asintfromx1's typeX<int>. This gives usfn<X<double>, int>(X<int> const&), which perfectly matches your call and returnsX<double>{2.0}—so this is the overload that gets selected.
To recap: the first overload is eliminated here due to a type mismatch after instantiation, not a deduction failure like in the first case.
内容的提问来源于stack exchange,提问作者Holt

