I think template functions can have default arguments parameters (not template parameters but runtime parameters). We can also initialize a class with an empty bracket initialization. But how does the compiler match the template ?
Why does this code compiles, how does the compiler make the deduction and what s Args in this function call example ?
What I have understand:
The default bracket initialization call the empty constructor, implicitly created because there is no user-defined constructor or user-defined default constructor. That is, we can initialize any pack with {}.. So the deduction don't apply there because we can't choose one pack, every pack is candidate. Maybe the default variadic template argument is <> (no arguments).
template<typename...> class pack {};
template<class... Args>
inline auto make(pack<Args...> = {}) {
}
int main() { make(); }
(compiled with GCC)
Note: I thought it wasn't, but default argument can be useful: 2 methods of calling the function: make < int, char, int >() (normal use) or make(myPack) for packing a variadic.
Given make();, the deduced Args is empty; make(); has the same effect as make<>(); in this case.
The template parameter is a parameter pack, and no template arguments are provided here. Note that function default arguments don't participate in template argument deduction. Then Args is deduced as empty.
If a parameter pack appears as the last P, then the type P is matched against the type A of each remaining argument of the call. Each match deduces the template arguments for the next position in the pack expansion:
Type template parameter cannot be deduced from the type of a function default argument:
Related
template <typename... T>
struct X {
static_assert(sizeof...(T) != 0);
};
template <typename... T>
void f(const X<T...> &) {}
template <typename T>
void inner() {}
int main() {
f(inner);
}
The static assert fires in this example.
Why does the compiler try to deducing anything here? And then it apparently even tries to instantiate X (with empty type argument list?..)...
Why the error isn't just 'template used without arguments'?..
If I change inner function to be a struct, the compiler reports:
use of class template 'inner' requires template arguments
which makes sense; if the param is just const X &, there's
declaration type contains unexpanded parameter pack 'T'
which also makes sense, however is less clear than the case with struct, because it reports an issue at the callee, not at the call site.
If the param is const X<T> &, the report is also a bit weird at first sight:
candidate template ignored: couldn't infer template argument 'T'.
These errors are for Clang 14, but GCC also reports similar ones.
Is the instantiation here somehow specified by the standard? If so, how? Also, why does it result in an empty type list?
This is mostly due to [temp.arg.explicit]/4, applicable when source names a function template, and emphasis mine:
Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack not otherwise deduced will be deduced as an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
The function template name as an argument means it is not used for template argument deduction of f ([temp.deduct.type]/(5.5.3)). So the pack T is not deduced from the function argument list (inner), and [temp.arg.explicit]/4 applies and deduces T as an empty list of types.
Now to evaluate the expression f(inner) involves converting the expression inner to the parameter type const X<>&. Whether and how this is valid depends on the constructors of class X<>, so the template is instantiated, causing the static_assert error since the template parameter pack does have zero elements.
Because the argument refers to an overload set that contains a function template X, no deduction is attempted from it. The hope is that the deduction will succeed anyway (perhaps from other arguments) and then the template arguments of X can be deduced from the resulting argument type. (Of course, it’s impossible to deduce the template argument for inner, but even that of
template<class T>
T make();
can be deduced in certain contexts, so it’s not in general a vain hope.)
Here, deduction for f does succeed vacuously, with T as an empty pack. (This is much like a default template argument.) Then const X<>& is the parameter type, and so overload resolution is attempted to construct an X<> from inner. That obviously depends on the constructors for X<>, so that type is completed and the compilation fails.
In my C++ travels I've come across the following idiom (for example here in Abseil) for ensuring that a templated function can't have template arguments explicitly specified, so they aren't part of the guaranteed API and are free to change without breaking anybody:
template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
And it does seem to work:
foo.cc:5:3: error: no matching function for call to 'AcceptSomeReference'
AcceptSomeReference<char>('a');
^~~~~~~~~~~~~~~~~~~~~~~~~
foo.cc:2:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'ExplicitArgumentBarrier'
I understand at an intuitive level why this works, but I'm wondering how to make it precise. What section(s) of the standard guarantee that there is no way to explicitly specify template arguments for this template?
I'm surprised by clang's excellent error message here; it's like it recognizes the idiom.
The main reason that a construct of the form
template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(T const&);
prohibits you from specifying the template argument for T explicitly is that for all template parameters that follow a template parameter pack either the compiler must be able to deduce the corresponding arguments from the function arguments (or they must have a default argument) [temp.param]/14:
A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list ([dcl.fct]) of the function template or has a default argument ([temp.deduct]). A template parameter of a deduction guide template ([temp.deduct.guide]) that does not have a default argument shall be deducible from the parameter-type-list of the deduction guide template.
As pointed out by #DavisHerring this paragraph alone does not necessarily mean that it must be deducted. But: Finding the matching template arguments is performed in several steps: First the explicitly specified template argument list will be considered [temp.deduct.general]/2, only later on the template type deduction will be performed and finally default arguments are considered [temp.deduct.general]/5. [temp.arg.explicit]/7 states in this context
Note 3: Template parameters do not participate in template argument deduction if they are explicitly specified
This means whether a template argument can be deducted or not depends not only on the function template declaration but also on the steps before the template argument deduction is applied such as the consideration of the explicitly specified template argument list. A template arguments following a template parameter pack can't be specified explicitly as then it would not be participating in template argument deduction ([temp.arg.explicit]/7) and therefore would also not be deducted (which would violate [temp.param]/14).
When explicitly specifying the template arguments the compiler will therefore match them "greedily" to the first parameter pack (as for the following arguments either default values should be available or they should be deductible from function arguments! Counter-intuitively this is even the case if the template arguments are type and non-type parameters!). So there is no way of fully explicitly specifying the template argument list of a function with a signature
template <typename... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
If it is called with
AcceptSomeReference<int,void,int>(8.0);
all the types would be attributed to the template parameter pack and never to T. So in the example above ExplicitArgumentBarrier = {int,void,int} and T will be deducted from the argument 8.0 to double.
To make things even worse you could use a reference as template parameter
template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
It isn't impossible to explicitly specify template arguments for such a barrier but reference template parameters are very restrictive and it is very unlikely to happen by accident as they have to respect [temp.arg.nontype]/2 (constexpr for non-types) and [temp.arg.nontype]/3 (even more restrictive for references!): You would need some sort of static variable with the correct cv-qualifier (e.g. something like static constexpr int x or static int const x in the following example would not work either!) like in the following code snippet (Try it here!):
template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T& t) {
std::cout << t << std::endl;
return;
}
static int x = 93;
int main() {
AcceptSomeReference<x>(29.1);
return EXIT_SUCCESS;
}
This way by adding this variadic template of unlikely template arguments as the first template argument you can hold somebody off from specifying any template parameters at all. Whatever the users will try to put will very likely fail compiling. Without an explicit template argument list ExplicitArgumentBarrier will be auto-deducted to have a length of zero [temp.arg.explicit]/4/Note1.
Additional comment to #DavisHerring
Allowing somebody to explicitly specify the template arguments following a variadic template would lead to ambiguities and break existing rules. Consider the following function:
template <typename... Ts, typename U>
void func(U u);
What would the template arguments in the call func<double>(8.0) be? Ts = {}, U = double or Ts = {double}, U = double? The only way around this ambiguity would be to allow the user to only specify all the template arguments explicitly (and not only some). But then this leads again to problems with default arguments:
template <typename... Ts, typename U, typename V = int>
void func2(U u);
A call to func2<double,double>(8.0) is again ambiguous (Ts = {}, U = double, V = double or Ts = {double}, U = double, V = int). Now you would have to ban default template arguments in this context to remove this ambiguity!
The standard is very vague about several aspects of template argument usage, including which template parameter “corresponds” to each argument. [temp.arg]/1 merely says
When the parameter declared by the template is a template parameter pack, it will correspond to zero or more template-arguments.
which can be read as saying that any and all template arguments (past those for any prior template parameters) not just can but do correspond to the template parameter pack.
Note that it is still possible to specify template arguments for the pack explicitly—the point is that it’s useless, since you can’t for the following (i.e., meaningful) template parameters. The use of a type like int& is just to make it less likely that you would succeed in doing so accidentally and think it accomplished something.
template<typename ...T, typename U>
void fun(U){}
int main(){
fun(0);
}
This snippet code is accepted by both GCC and Clang. The template parameter pack T does not participate in the template argument deduction in the context of function call, as per the following rules:
[temp.deduct.call]
Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below.
The pack T is contained by any function template parameter. If there were no other special rules specify, the deduction would fail according to:
[temp.deduct.type#2]
if any template argument remains neither deduced nor explicitly specified, template argument deduction fails.
However, such a case is ruled by the following rule in the current standard, that is:
[temp.arg.explicit#4]
A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments.
So, the above case can be considered to deduce successfully which leaves the pack T with an empty set of template arguments.
However, the special rule in temp.arg.explicit#4 has been changed to a note in the current draft
[temp.arg.explicit#note-1]
[Note 1: A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. — end note]
So, I wonder Is there any alternative normative rule in the current draft states that the pack T not otherwise deduced will be deduced to an empty set of template arguments?
The previously normative section of [temp.arg.explicit]/4
[...] A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. [...]
was made into a non-normative note as part of P1787R6.
As you've pointed out, as per [temp.deduct.type]/2 [emphasis mine]:
Type deduction is done independently for each P/A pair [...], if any template argument remains neither deduced nor explicitly specified, template argument deduction fails.
[temp.arg.general] describes that a template parameter that is a template parameter pack may correspond to zero template arguments:
[...] When the parameter declared by the template is a template parameter pack, it will correspond to zero or more template-arguments.
and [temp.variadic]/1 explicitly mention that a template parameter pack may accept zero arguments:
A template parameter pack is a template parameter that accepts zero or more template arguments.
followed by a non-normative example of an empty argument list for an entity templated over a parameter pack template parameter:
template<class ... Types> struct Tuple { };
Tuple<> t0; // Types contains no arguments
Now, returning to [temp.arg.explicit]/4:
If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
Meaning the Tuple example above is can likewise omit the empty argument list
Tuple t0; // Also OK: Types contains no arguments
but where the key is that, as per [temp.arg.general] above, that a template parameter list may correspond to zero arguments, in which case there are no template arguments needed to be deduced.
If you look at your own example:
template<typename ...T, typename U>
void fun(U){}
int main(){
fun(0); // #1
}
you could likewise invoke #1 as:
fun<>(0); // argument list for parameter pack is empty
// -> no argument (beyond that for `U`) to deduce
highlighting that the deducible argument corresponding to the template parameter U can be omitted from the explicit template-arguments, whereas the remaining template-arguments are none; namely, the argument list for the template parameter that is a template parameter pack is empty, and there are thus no remaining template arguments that needs to be deduced.
Thus
A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments.
is non-normative/redundant, explaining why it was wrapped into a [Note - [...] - end note].
The following code implements a function template foo that accepts an arbitrary number of arguments, and subsequently handles each one while maintaining a positional index of that argument:
template<int index, typename T>
void foo_impl(T value)
{
// Do something with index/value
}
template<int index, typename T, typename... Rest>
void foo_impl(T value, Rest... values)
{
// Do something with index/value
// Recursively handle remaining arguments
foo_impl<index + 1>(values...);
}
template<typename... T>
void foo(T... args)
{
foo_impl<1>(args...);
}
int main()
{
foo("test", 42);
}
This recursively instantiates function templates until it reaches the base template that takes a single argument. Every function template instantiation of foo_impl omits the template type arguments. Although this compiles with Clang, GCC, and MSVC, I'm not sure this is legal.
Is it legal to omit the template arguments as illustrated in the code example? If so, what are the specific rules? And have those rules changed between C++ Standards?
The specific rule is in [temp.arg.explicit]
3 Trailing template arguments that can be deduced or obtained
from default template-arguments may be omitted from the list of
explicit template-arguments. A trailing template parameter pack not
otherwise deduced will be deduced to an empty sequence of template
arguments. If all of the template arguments can be deduced, they may
all be omitted; in this case, the empty template argument list <>
itself may also be omitted. In contexts where deduction is done and
fails, or in contexts where deduction is not done, if a template
argument list is specified and it, along with any default template
arguments, identifies a single function template specialization, then
the template-id is an lvalue for the function template specialization.
Since the type arguments are trailing, and can be deduced from the arguments of the function call, they may be omitted.
Such verbiage exists in all standard revisions to date (except for the bit about parameter packs, which isn't there in C++03).
g++, clang++, and MSVC (pre 2018) all accept the following C++17 code, resulting in the output "unsigned int" then "int":
#include <iostream>
void print_type(int) { std::cout << "int\n"; }
void print_type(unsigned int) { std::cout << "unsigned int\n"; }
template <typename ...T>
void print_types(T ...args)
{
(print_type(args),...);
}
int main()
{
print_types<unsigned int>(1, 1);
}
I agree that this ought to work this way, but I'm having trouble finding a description of why and exactly how in the Standard.
First there's [temp.deduct]/2 describing the processing of explicit template arguments before doing the rest of template argument deduction:
[T]he following steps are performed when evaluating an explicitly specified template argument list with respect to a given function template:
... There must not be more arguments than there are parameters unless at least one parameter is a template parameter pack, and there shall be an argument for each non-pack parameter....
The specified template argument values are substituted for the corresponding template parameters as specified below.
In the example, unsigned int is certainly a "specified template argument value". But if its "corresponding template parameter" T gets substituted now, it's difficult to see how it could become a longer list of types later.
For the template argument deduction process, there's [temp.deduct.call]/1:
For a function parameter pack that occurs at the end of the parameter-declaration-list, deduction is performed for each remaining argument of the call, taking the type P of the declarator-id of the function parameter pack as the corresponding function template parameter type. Each deduction deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack.
I take "remaining argument of the call" here to mean arguments after the ones that correspond to function parameters that are not the final function parameter pack. But that would mean that in my example, the first function argument 1 is used to deduce T=int. Does this deduction actually happen, but then get discarded/overridden by the T=unsigned int from the explicit template argument?
Or maybe "remaining argument of the call" is supposed to mean function arguments after the ones that do not correspond to the final function parameter pack AND after any that correspond to parameter types generated from explicit template arguments; and "subsequent positions in the template parameter packs expanded by the function parameter pack" is supposed to mean sequential positions after any filled by explicit template arguments, but this would be far from clear. And if so, it's also confusing that there is a list of parameter types associated with the function parameter pack but it's still a function parameter pack.
[Another possible implementation giving the expected behavior would be: when one or more explicit template arguments A_1, ..., A_k correspond to a template parameter pack P, invent another template parameter pack More_P of the same kind, and substitute each expansion of P with the template argument list {A_1, ..., A_k, More_P...}. Then More_P can be deduced like any other template parameter pack. If More_P is never deduced, substitute an empty list for all its expansions before evaluating semantics as for all other deduced substitutions. But there's even less justification for this interpretation in the Standard.]
Have I missed something in the Standard that better describes how explicit template arguments and deduced template arguments can work together to form a single list for one template parameter pack?
It's [temp.arg.explicit]/8:
Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:
template<class ... Types> void f(Types ... values);
void g() {
f<int*, float*>(0, 0, 0); // Types is deduced to the sequence int*, float*, int
}
— end example ]