Template argument deduction when mixing variadic template with C-style variadic function
S

1

7

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.

Serajevo answered 21/10, 2021 at 9:32 Comment(5)
I would have said gcc, as (&foo<int>) should be done first, so cannot use later deduction...Carrie
I think Clang and MSVC are tripping over their own optimizations. Taking the address and immediately calling a regular function can be inlined to... just calling it. This doesn't apply here however, since now taking the address first affects behavior.Topazolite
Indeed, adding a temporary variable into the mix auto p = &foo<int>; makes both Clang and MSVC behave as one would expect.Topazolite
@StoryTeller-UnslanderMonica: [over.match.call.general]/2 says that overload resolution can proceed for the address of an overload set, so template argument deduction could quite reasonably happen here.Oasis
@DavisHerring - Our definitions of "reasonable" do not coincide.Topazolite
T
2

Edit: after the question was edited, it now comprises two orthogonal sub-questions, which I've handled separately.

Given 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.

Do calls of the form (&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, where F names a set of overloaded functions. In the context of a function call, &F is treated the same as the name F by itself. Thus, (&F)( expression-listopt ) is simply F( 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.

Triglyph answered 24/10, 2021 at 20:41 Comment(6)
Thanks a lot, Columbo. So, if I correctly understand you, the correct output should be 2. Am I right?Serajevo
That's what it appears like.Triglyph
So it reasonably seems a bug on GCC. Also ICC returns 2.Serajevo
Very interestingly in support of yout thesis, the behavior is the same if the foo is not called by reference, i.e. if I use return foo<int>(1, 2); directly. I've edited the question.Serajevo
@GiovanniCerretani I edited the answer to cover both aspects of this question.Triglyph
Great. I've just submitted this bug report to GCC Bugzilla: gcc.gnu.org/bugzilla/show_bug.cgi?id=102928Serajevo

© 2022 - 2024 — McMap. All rights reserved.