| Document #: | P3606R2 [Latest] [Status] |
| Date: | 2026-09-18 |
| Project: | Programming Language C++ |
| Audience: |
Core Working Group |
| Reply-to: |
Corentin Jabot <corentin.jabot@gmail.com> Younan Zhang <zyn7109@gmail.com> |
During overload resolution, if GCC finds an exact, non-ambiguous, non-template match, it will pick that overload and will not perform template argument deduction.
template<typename T>
decltype([] { return T::x;}) f(T); // #1
void f(int) {} // #2
int main() {
f(0);
}In this example, GCC will pick
#2 (which is
an exact match for all arguments) when calling
f(0).
GCC will not perform template argument deduction for
#1.
In other implementations, this is ill-formed.
Indeed, in other implementations,
#1 will be
instantiated before overload resolution is performed, and
T::x results
in a substitution failure in a non-immediate context (body of a lambda),
which is a hard error.
The conformity of GCC’s behavior hinges on a generous interpretation of [temp.inst]/9
“If the function selected by overload resolution can be determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.”
Note that the CWG consensus is that GCC’s behavior is probably valid by the spirit of the law but not its letter. In any case, CWG generally considers whether to mandate GCC’s behavior as an evolutionary matter.
Is it? After all, EWG might not care about whether diagnostics are produced in non-immediate contexts during overload resolution. Let’s make things more riveting with concepts. Consider
struct S {
template <typename T>
requires std::copyable<T>
explicit S(T op) noexcept; // #1
S(const S&) noexcept = default; // #2
};
static_assert(std::copyable<S>);This snippet is a reduction of code that is regularly submitted as an issue to implementations.
To check whether S satisfies
copyable, we need to check that
S(declval<const S&>())
is a valid expression. To that end, we perform overload resolution
between #1
and #2 after
instantiating
#1 with
[T=S]. #1 is
constrained by copyable.
To understand whether S satisfies
copyable, one must first understand
whether S satisfies
copyable. The constraint depends on
itself, and the program is ill-formed.
The user presumably wanted to write
template <typename T>
requires (!std::same_as<T, S> && std::copyable<T>)
explicit S(T op) noexcept;But why didn’t they? Probably because GCC is perfectly happy with
this program. Indeed, when GCC performs overload resolution,
#2 is a
perfect match,
#1 is never
instantiated, and its constraints are never checked.
Concept checking, in particular for concepts that could lead to recursion, makes GCC’s optimization very much visible even without considering errors in non-immediate contexts.
Because of that, we think the standard needs to clarify the allowed implementation strategies. But this has been the status quo for years. Why write this paper now?
Clang tried to implement the very useful [CWG2369]. However, this unearthed the opposite issue. Consider
template <typename U>
struct B { static_assert(false); };
template <typename T>
requires (sizeof(B<T>) == 1)
void f(T, typename T::foo = 0) {} // #1
void f(int) {} // #2
f(0);Again, this code is reduced from actual bug reports. In particular, variations of that code have been found in the standard library specification, libc++, and MSSTL ([LWG4139]).
All compilers compile that code at the time of writing. As usual, GCC
will pick #2
because it’s an exact match, and
#1 is,
therefore, never instantiated.
Clang, however, will try to instantiate
#1. Because
[CWG2369] is not implemented
in Clang, we will substitute into the argument
T::foo
before evaluating the constraint. Because
int::foo
is not a valid type,
#1 is
SFINAE’d away, and we pick
#2.
Therefore, existing code happens to be valid for both GCC and Clang for different reasons, and that makes it difficult for Clang to deploy [CWG2369], even if that would be a very useful defect report to implement.
This brings us to this paper.
Adopting GCC’s behavior, i.e., not considering template overload candidates when a non-template candidate is an exact match (i.e., there is an exact match for every argument) would have some benefits:
same_as constraint on templated
constructors. This pattern/mistake is fairly common (but then again,
this only happens because an implementation is more lenient than
others).The downside is that errors in the non-immediate context of template candidates would no longer be diagnosed. A small price to pay?
On the other hand, if we do not want to bless GCC’s behavior, it’s unclear how implementations would be able to deploy CWG2369 as a DR. Should we expect GCC to change its implementation? That would break a non-trivial amount of code.
Consider the following example from GCC’s bug tracker:
struct foo_tag{};
struct bar_tag{};
template <class T>
concept fooable = requires(T it) {
invoke_tag(foo_tag{}, it);
};
template<class T>
T invoke_tag(foo_tag, T in); // #1
template<fooable T>
T invoke_tag(bar_tag, T it); // #2
int main() {
// Neither line below compiles in GCC 11, independently of the other
return invoke_tag(foo_tag{}, 2);
return invoke_tag(bar_tag{}, 2);
}In that scenario, both invoke_tag
overloads are templates, so all implementations instantiate both
overloads. After CWG2369,
fooable<T>
is checked for both overloads before looking at the validity of function
arguments; that will cause the constraints of
#2 to be
rechecked during satisfaction.
fooable<T>
always depends on itself, and the program is ill-formed.
So GCC found that CWG2369 broke existing code (the above is a
reduction from tag_invoke). To
remediate this, they changed the order of template argument deduction to
look at the arguments for which deduction would never produce an
instantiation before considering constraints.
Doing so, in the example above, when checking the satisfaction of
fooable<T>,
GCC realizes
#2 is not a
valid overload for
invoke_tag(foo_tag{}, it),
and picks
#1, which is
not constrained, and therefore, there is no recursive checking, and both
calls become valid.
No CWG issue was created for that, and it’s unclear if it is EWG territory, but it is worth mentioning as GCC employed a similar strategy to the one they use for overload resolution to work around the breakages caused by CWG2369.
During overload resolution, if there is a viable non-template candidate for which every argument’s conversion is an exact match, then template candidates are not considered and argument deduction/substitution does not happen.
All strategies described here were implemented by at least one implementation, which is exactly the issue!
Our main objective with this paper is to clarify intent so that we can facilitate the implementation of CWG2369 (and [CWG2769]) in Clang.
7 In each case where conversion functions of a class
Sare considered for initializing an object or reference of typeT, the candidate functions include the result of a search for the conversion-function-idoperator TinS. Any specialization of a conversion function template found by this search is potentially generated ([temp.over]). [. . .]8 In each case where a candidate is a function template, candidate function template specializations are potentially generated using template argument deduction ([temp.over], [temp.deduct]). If a constructor template or conversion function template has an explicit-specifier whose constant-expression is value-dependent ([temp.dep]), template argument deduction is performed first and then, if the context admits only candidates that are not explicit and the generated specialization is explicit ([dcl.fct.spec]), it will be removed from the candidate set. Those candidates are then handled as candidate functions in the usual way.93 A given name can refer to, or a conversion can consider, one or more function templates as well as a set of non-template functions. In such a case, the candidate functions generated from each function template are combined with the set of non-template candidate functions.
1 Define ICSi(
F) as the implicit conversion sequence that converts the ith argument in the list to the type of the ith parameter of viable functionF. [over.best.ics] defines the implicit conversion sequences and [over.ics.rank] defines what it means for one implicit conversion sequence to be a better conversion sequence or worse conversion sequence than another.2 Given these definitions, a viable function
F1 is defined to be a better function than another viable functionF2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
- [. . .]
2a A viable function F is a perfect viable function if
- ((2a.1)) F is not a function template specialization,
- ((2a.2)) for all arguments i, ICSi(F) is a perfect conversion sequence ([over.ics.scs]), and
- ((2a.3)) if F is a conversion function, the implicit conversion sequence from the return type of F to the type of the object being initialized ([over.match.conv]) is a perfect conversion sequence.
2b Let S be the set of viable functions among the candidates that are not function templates. If
- ((2b.1)) there is exactly one perfect viable function F in S that is better than all other viable functions in S,
- ((2b.2)) the overload resolution is not performed for a copy-initialization ([over.match.copy]), and
- ((2b.3)) either F is not a conversion function or no candidate is a constructor template,
then F is the one selected by overload resolution.
Otherwise, the potentially generated candidate function template specializations ([temp.over]) are generated and added to the set of viable functions if they are viable ([over.match.viable]).
3 If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution; otherwise the call is ill-formed.99
[ Example: [. . .] — end example ]
[ Note: If the best viable function was made viable by one or more default arguments, additional requirements apply ([over.match.viable]). — end note ]
3 Each conversion in Table 19 also has an associated rank (Exact Match, Promotion, or Conversion). These are used to rank standard conversion sequences. The rank of a conversion sequence is determined by considering the rank of each conversion in the sequence and the rank of any reference binding. If any of those has Conversion rank, the sequence has Conversion rank; otherwise, if any of those has Promotion rank, the sequence has Promotion rank; otherwise, the sequence has Exact Match rank.
[. . .]
4 A standard conversion sequence S is a perfect conversion sequence if
- ((4.1)) the argument expression is not the initializer-clause of a braced-init-list with a single initializer-clause,
- ((4.2)) S consists of the identity conversion or an lvalue transformation, and
- ((4.3)) if the parameter has type “reference to cv T” and binds directly to the argument expression, the argument expression has type cv T.
2 If there is no target, all non-template functions named are selected. Otherwise, a non-template function with type
Fis selected for the function typeFTof the target type ifF(after possibly applying the function pointer conversion ([conv.fctptr])) is identical toFT. [. . .]3 The specialization, if any, potentially generated by template argument deduction ([temp.over], [temp.deduct.funcaddr], [temp.arg.explicit]) for each function template named is added to the set of selected functions considered.
1 When a call of a function or function template is written (explicitly, or implicitly using the operator notation), a function template specialization is potentially generated from each function template. For each function template whose potentially generated specialization is required to determine the result of overload resolution ([over.match.best.general], [over.over]), template argument deduction ([temp.deduct]) and checking of any explicit template arguments ([temp.arg]) are performed
for each function templateto find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments or, for conversion function templates, that can convert to the required type.For each such function template:
- ((1.1)) 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.
- ((1.2)) If the argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template.
The complete set of candidate functions includes all the synthesized declarations and all of the non-template functions found by name lookup. The synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in [over.match.best].114
Thanks to people who contributed to the various discussions on this topic on the Core reflector, and the LLVM and GCC bug trackers. Thanks to Vlad Serebrennikov for his help writing the wording.