Giovanni Cerretani
Giovanni Cerretani

Reputation: 1724

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

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

Answers (1)

Columbo
Columbo

Reputation: 60999

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.

Upvotes: 2

Related Questions