Reputation: 1724
Inspired by this answer, I produced this code whose output depends on the compiler:
template <typename... Args>
constexpr auto foo(Args&& ...args, ...) noexcept {
return sizeof...(args);
}
constexpr auto bar() noexcept {
return (&foo<int>)(1, 2);
}
If compiled with GCC 11, bar
calls foo<int>
and returns 1, while both clang 13 and MSVC 2019 deduce foo<int, int>
and bar
returns 2.
This is my sandbox on godbolt: https://godbolt.org/z/MedvvbzqG.
Which output is correct?
EDIT:
The misbehavior persists if I use return foo<int>(1, 2);
directly, i.e. with
constexpr auto bar() noexcept {
return foo<int>(1, 2);
}
Sandbox updated: https://godbolt.org/z/Wj757sc7b.
Upvotes: 7
Views: 497
Reputation: 60999
Edit: after the question was edited, it now comprises two orthogonal sub-questions, which I've handled separately.
foo<int>(1, 2)
, should the parameter pack be deduced to cover all args?Yes. The parameter pack does occur at the end of the parameter-declaration-list, which is the criterion for whether it's non-deduced or not. This was actually clarified in CWG issue 1569. We can convince ourselves by observing that all compilers agree this is fine:
template <typename... Args>
constexpr auto foo(Args&& ...args, ...) noexcept {
return sizeof...(args);
}
static_assert(2 == foo(1, 2), "always true");
Only when we change foo
to foo<int>
, GCC suddenly stops deducing the pack. There's no reason for it to do so, explicitly supplying template arguments to a pack should not affect whether it's eligible for deduction.
(&T<...>)(...)
still invoke template argument deduction?The answer is yes, as discussed in the open CWG issue 1038:
A related question concerns an example like
struct S { static void g(int*) {} static void g(long) {} } s; void foo() { (&s.g)(0L); }
Because the address occurs in a call context and not in one of the contexts mentioned in 12.3 [over.over] paragraph 1, the call expression in foo is presumably ill-formed. Contrast this with the similar example
void g1(int*) {} void g1(long) {} void foo1() { (&g1)(0L); }
This call presumably is well-formed because 12.2.2.2 [over.match.call] applies to “the address of a set of overloaded functions.” (This was clearer in the wording prior to the resolution of issue 704: “...in this context using &F behaves the same as using the name F by itself.”) It's not clear that there's any reason to treat these two cases differently.
As the note explains, prior to issue 704 we had this very explicit section:
The fourth case arises from a postfix-expression of the form
&F
, whereF
names a set of overloaded functions. In the context of a function call,&F
is treated the same as the nameF
by itself. Thus,(&F)(
expression-listopt)
is simplyF(
expression-listopt)
, which is discussed in 13.3.1.1.1.
The reason this wording ended up being removed is not that it was defective, but that the entire section was poorly worded. The new wording still explicitly states that overload resolution is applied to an address of an overloaded set (which is what we have here):
If the postfix-expression denotes the address of a set of overloaded functions and/or function templates, overload resolution is applied using that set as described above.
Upvotes: 2