Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 268
Giovanni Cerretani Avatar asked Oct 21 '21 09:10

Giovanni Cerretani


People also ask

How do you use Variadic arguments?

In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed. The variadic function consists of at least one fixed variable and then an ellipsis(…) as the last parameter. This enables access to variadic function arguments.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

Is printf Variadic function?

Variadic functions are functions (e.g. printf) which take a variable number of arguments.

Can Variadic methods take any number of parameters?

A variadic function allows you to accept any arbitrary number of arguments in a function.

What are variadic function templates in C++?

Variadic function templates in C++. Variadic templates are template that take a variable number of arguments. Variadic function templates are functions which can take multiple number of arguments.

Can a template parameter be deduced from a default argument?

Type template parameter cannot be deduced from the type of a function default argument: Deduction of template template parameter can use the type used in the template specialization used in the function call: Besides function calls and operator expressions, template argument deduction is used in the following situations:

What is template argument deduction in C++?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

What are variadic arguments in C++?

Douglas Gregor and Jaakko Järvi came up with this feature for C++. Variadic arguments are very similar to arrays in C++. We can easily iterate through the arguments, find the size (length) of the template, can access the values by an index, and can slice the templates too.


Video Answer


1 Answers

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.

like image 157
Columbo Avatar answered Oct 21 '22 03:10

Columbo