Does anyone know if the following implicit capture of 'ts' is well-formed:
template<class ... Ts> void bar(Ts ... ts) { }
template<class ... Ts> int foo(Ts ... ts) {
auto L = [=] () {
bar(ts...);
};
L();
return 0;
}
int g = foo(1, 2, 3);
Does the standard clearly state anywhere that this should not be well formed?
14.5.3/6:
The instantiation of a pack expansion that is not a sizeof... expression produces a list E1, E2, ..., EN , where
N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. All of the Ei become elements in the enclosing list.
Regardless of whether you're allowed to explicitly capture a pack (you can, using [ts ...]), the general rule of expansion will result in capture of each element of the list.
I guess it's well formed, I've not found a straight statement (the wording sometimes lacks in clarity / illustration for certain situations) but I guess it may be inferred:
§5.1.2/23:
A capture followed by an ellipsis is a pack expansion (14.5.3). [ Example:
template<class... Args>
void f(Args... args) {
auto lm = [&, args...] { return g(args...); };
lm();
}
— end example ]
A capture followed by an ellipsis, implies args, in the lambda-capture, is an example of a capture (in this case, explicit), and the notable fact is that args is a parameter pack identifier. This short paragraph has the sole job of describing how lambda-captures hold pack expansions, which demonstrates parameter packs can be captured even though its purpose is not about allowing them to be captured.
§5.1.2/12:
An entity is captured if it is captured explicitly or implicitly.[...]
§3/3:
An entity is a value, object, reference, function, enumerator, type, class member, template, template specialization, namespace, parameter pack, or this.
From this I assume parameter packs are entities that can be captured explicitly or implicitly, and so, the same capturing rules as for ordinary variables shall apply, except that parameter packs shall be expanded accordingly.
I guess your question (and the same argumentation) could be applied equally well for reference variables for example (It is unspecified whether or not a reference requires storage. §8.3.2/4). It seems you're interested when you're allowed or not to refer to a parameter pack identifier inside a lambda.
You can think the same about reference variables in the outer scope since you may have access to them but couldn't even be allowed to access the identifier of the original variable.
They're as ethereal as parameter packs.
Related
Having following functions f0, f1, f2 in C++14 code, which accepts arbitrary number of fixed-length arrays:
#include <functional>
template<typename... TS, size_t N> void f0( TS(&& ... args)[N] ) {}
template<typename T, size_t... NS> void f1( T(&& ... args)[NS] ) {}
template<typename... TS, size_t... NS> void f2( TS(&& ... args)[NS] ) {}
int main(){
f0({1,2}, {3.0,4.0}, {true, false});
f1({1,2,3}, {4,5}, {6});
f2({1,2,3}, {4.0,5.0}, {true});
return 0;
}
Function f0 accepts arrays with different types and fixed array length. Function f1 accepts arrays with fixed type and different array lengths. It's clear how this works: C++ compiler deduces variable-length parameter pack in immediate context of template function instantiation, which is expanded in (&& ... args) expression.
Function f2 accepts arrays with different types and different array lengths, which produces two variable-length parameter packs, however there is only one ellipsis operator in pack expansion (&& ... args), but code compiles and works well.
So question is: what is general rule for expanding multiple parameter packs within single ellipsis operator? Obviously, at a minimum, they must be the same length, but what are the other requirements? Is there a precise definition that the n-th element of the first parameter packing should expand along with the n-th element of the second parameter packing?
Also, following code with explicit template argument provision does not compile: f2<int,float,bool,3,2,1>({1,2,3},{4.0f,5.0f},{true});. It would be interesting to know the reasons for this behaviour.
This is specified in C++ Standard section [temp.variadic]. Basically, it's what you described: when a pack expansion expands more than one pack, all those packs must have the same number of elements. And the expansion in most cases forms a list where the nth element in the resulting list uses the nth element of each expanded pack.
More exactly, paragraph 5 defines
A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:
In a function parameter pack; the pattern is the parameter-declaration without the ellipsis.
...
In your example, each function template declares a function parameter pack named args. The patterns are TS(&& args)[N], T(&& args)[NS], and TS(&& args)[NS].
Paragraph 7 (after clarifying which packs are expanded by which pack expansions that when one pack expansion appears inside another) has the requirement
All of the packs expanded by a pack expansion shall have the same number of arguments specified.
And paragraph 8:
The instantiation of a pack expansion that is neither a sizeof... expression nor a fold-expression produces a list of elements E1, E2, ..., EN, where N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element.
So yes, for the instantiation of f3 where TS is deduced as int, double, bool and NS is deduced as 3, 2, 1, the pack expansion becomes a function parameter list with types int(&&)[3], double(&&)[2], bool(&&)[1].
All packs appearing as part of one pack expansion (...) must have exactly the same length. Otherwise substitution fails (which depending on context is a hard error or SFINAE). (see [temp.variadic]/7)
All packs are expanded so that the i-th expanded element of the pack expansion uses the i-th element of each pack. For the detailed expansion rule see [temp.variadic]/8.
(Links are to the post-C++20 draft of the standard, but the same applies to C++14.)
A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.
A template with at least one parameter pack is called a variadic template.
Visit[1]: https://en.cppreference.com/w/cpp/language/parameter_pack
Visit[2]: Multiple expansions of multiple parameter packs in the same expression
Visit[3]: https://www.ibm.com/docs/en/zos/2.1.0?topic=only-variadic-templates-c11
I hope this help you.
All standard references below refers to N4861 (March 2020 post-Prague working draft/C++20 DIS).
Background
In the Q&A Are captureless lambdas structural types? it was made clear that certain lambda-expressions have associated closure types that are (literal and) structural types, such that a particular such closure type may be used as non-type template parameter; essentially passing structural type lambdas as non-type template parameters.
template<auto v>
constexpr auto identity_v = v;
constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;
Now, according to [expr.prim.lambda.closure]/1 the type of each lambda-expression is unique
[...] a unique, unnamed non-union class type, called the closure type [...]
On the other hand, [basic.def.odr]/1 [extract, emphasis mine] states
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argument.
arguably meaning that default template arguments are considered definitions that need to respect the ODR.
Question
... which leads to my question:
Is a lambda expression a legal default (non-type template) argument and, if so, wouldn't this imply that each instantiation using such a default argument instantiates a unique specialization?
(Please highlight also if near-illegal: e.g. if anything beyond a single instantiation would lead to an ODR-violation).
Why?
If this is in fact legal, each invocation of say a variable template with a lambda as default argument would result in an instantiation of a unique specialization:
template<auto l = [](){}>
// ^^^^^^ - lambda-expression as default argument
constexpr auto default_lambda = l;
static_assert(!std::is_same_v<
decltype(default_lambda<>),
decltype(default_lambda<>)>);
Both GCC (DEMO) and Clang (DEMO) accepts the program above
If the compilers are correct to accept this example, this means allowing another mechanism to capture and retrieve a meta-programming state, a technique that has since long been deemed, as per CWG open issue 2118, as
... arcane and should be made ill-formed.
Is a lambda expression a legal default (non-type template) argument and, if so, wouldn't this imply that each instantiation using such a default argument instantiates a unique specialization?
Yes, it means that given your templated variable:
template<auto l = [](){}>
constexpr auto default_lambda = l;
every call to default_lambda will produce a new template instantiation by generating a new unique closure type and thus provides a new way to capture a metaprogramming state.
Thus, every expressions that use default_lambda have to be reevaluated. If the state of the compiler changed since the last instantiation and if we used it in a dependent expression (ex: check that a type is defined) then the result of the expression might change.
For example:
struct X; // not defined
template <typename T, auto = default_lambda<>>
consteval bool is_defined() {
if constexpr (requires { T{}; }) {
return true;
} else {
return false;
}
}
static_assert(is_defined<X>() == false);
struct X {};
static_assert(is_defined<X>() == true);
This might look similar to "I cannot pass lambda as std::function", but I'm actually passing the std::function parameter by value, so that problem doesn't apply. I've defined the following function.
template<typename T>
std::vector<T> countSort(const std::vector<T> &v, std::function<int(T)> keyFunc, int n);
The second parameter is an std::function that maps T to int (passed by value).
When calling this, I wanted to use a lambda expression, as follows:
std::vector<int> v;
[...]
v = countSort(v, [](int x) { return x; }, 10);
But the template argument deduction fails, because "main()::<lambda(int)> is not derived from std::function<int(T)>". It does work if I specify the template argument, or if I introduce an intermediate variable of type std::function for the lambda expression:
std::function<int(int)> lambda = [](int x) { return x; };
v = countSort(v, lambda, 10);
Why can't I do the former? I'm giving the compiler the exact same information; if it is able to convert a value of type lambda<int> to std::function<int(int)> when assigning it to a variable, why can't it directly convert from lambda<int> to the parameter type, which is std::function<T(int)>—and taking into account that v is of type std::vector<int>, it should know that T is int? The whole reason I want to use a lambda expression is precisely that, it's an expression, so I should be able to write it inline in the function call argument list, without having to give it a name or assign it to a variable.
The problem is, template argument deduction doesn't consider implicit conversion (from lambda to std::function), which causes the deduction for T on the 2nd function parameter keyFunc to fail.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
You can use std::type_identity (since C++20) to exclude the 2nd function parameter from deduction. e.g.
template<typename T>
std::vector<T> countSort(const std::vector<T> &v, std::function<int(std::type_identity_t<T>)> keyFunc, int n);
BTW: If your compiler doesn't support std::type_identity, it's not hard to make one.
And about how std::type_identity works here, see non-deduced context:
(emphasis mine)
In the following cases, the types, templates, and non-type values that
are used to compose P do not participate in template argument
deduction, but instead use the template arguments that were either
deduced elsewhere or explicitly specified. If a template parameter is
used only in non-deduced contexts and is not explicitly specified,
template argument deduction fails.
The nested-name-specifier (everything to the left of the scope
resolution operator ::) of a type that was specified using a
qualified-id:
This question already has answers here:
variadic template parameter pack expanding for function calls
(2 answers)
Closed 8 years ago.
In the code below I expand a parameter pack inside an initializer list, while calling a function DoSomethingReturnInt on each element. Below that I attempt to do something seemingly similar to try and call DoSomething on each element, but get a compiler error. Is this simply not possible or do I simply have to modify it slightly to accomplish this? It seems to me that something like this should be possible.
template <class T>
int DoSomethingReturnInt(T&& t)
{}
template <class T>
void DoSomething(T&& t)
{}
template <class... T>
void variadic(T&&... args)
{
int arr[] = { DoSomethingReturnInt(args)... }; //Compiles OK
DoSomething(args)...; //error: parameter packs not expanded with '...'
}
int main()
{
variadic("Testing", "one", 2.0, 3);
}
This is not a valid location for parameter pack expansion. The valid contexts for pack expansion is covered in the draft C++ standard section 14.5.3 Variadic templates which says:
A pack expansion consists of a pattern and an ellipsis, the
instantiation of which produces zero or more instantiations of the
pattern in a list (described below). The form of the pattern depends
on the context in which the expansion occurs. Pack expansions can
occur in the following contexts:
— In a function parameter pack
(8.3.5); the pattern is the parameter-declaration without the
ellipsis.
— In a template parameter pack that is a pack expansion (14.1):
— if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration
without the ellipsis;
— if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is
the corresponding type-parameter without the ellipsis.
— In an initializer-list (8.5); the pattern is an initializer-clause.
— In a base-specifier-list (Clause 10); the pattern is a base-specifier.
— In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
— In a template-argument-list (14.3); the pattern is a template-argument.
— In a dynamic-exception-specification (15.4); the pattern is a type-id.
— In an attribute-list (7.6.1); the pattern is an attribute.
— In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
— In a capture-list (5.1.2); the pattern is a capture.
— In a sizeof... expression (5.3.3); the pattern is an identifier.
This is also covered on cppreference section for Parameter pack.
You can use the comma operator to your advantage here.
int dummy[] = { (DoSomething(args), 0)... };
EDIT: If you don't like the comma operator "abuse", maybe a lambda?
int dummy[] = { []() { DoSomething(args); return 0; }()... };
Note that gcc4.9 doesn't seem to be able to handle this, but clang will do just fine.
Somehow I don't get how variadic template parameter packs are expanded. What's wrong with thie following code?
#include <iostream>
template <typename T>
struct print_one
{
static void run(const T& t)
{
std::cout << t << ' ';
}
};
template<typename... Args>
void print_all(Args&&... args)
{
// the next line doesn't compile:
print_one<Args>::run(std::forward<Args>(args))...;
}
int main()
{
print_all(1.23, "foo");
}
Clang says, Expression contains unexpanded parameter packs 'Args' and 'args'. Why?
The ... has to go inside the function call parentheses:
print_one<Args>::run(std::forward<Args>(args)...);
Obviously, that won't work for your function that takes only a single argument, so you need to find a way to expand the calls into a function call or other allowed construct:
// constructing a dummy array via uniform initialization
// the extra 0 at the start is to make it work when the pack is empty
int dummy[]{0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};
// or, if your compiler doesn't support uniform initialization
int dummy[] = {0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};
// or, calling a dummy function
template<typename... Args> void dummy(Args...) {}
dummy((print_one<Args>::run(std::forward<Args>(args)), 0)...);
// or, constructing a temporary dummy object
struct dummy { dummy(std::initializer_list<int>) {} };
dummy{(print_one<Args>::run(std::forward<Args>(args)), 0)...};
// or, constructing a temporary initializer list
std::initializer_list<int>{(print_one<Args>::run(std::forward<Args>(args)), 0)...};
Note the use of the comma operator to turn the void return of print_one into a value suitable to place in an argument list or initializer expression.
The initializer-list forms are preferred to the function call forms, as they are (supposed to be) ordered LTR which function call arguments are not.
The forms where a parameter pack expansion can occur are covered by 14.5.3 [temp.variadic]:
4 - [...] Pack expansions can occur in the following contexts:
[...]
Your original code is illegal because although textually it might appear that it should produce a statement consisting of a number of comma-operator expressions, that is not a context allowed by 14.5.3:4.
The standard dictates where pack expansion is allowed:
§14.5.3 [temp.variadic] p4
[...] Pack expansions can occur in the following contexts:
In a function parameter pack (8.3.5); the pattern is the parameter-declaration without the ellipsis.
In a template parameter pack that is a pack expansion (14.1):
if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration without the ellipsis;
if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is the corresponding type-parameter without the ellipsis.
In an initializer-list (8.5); the pattern is an initializer-clause.
In a base-specifier-list (Clause 10); the pattern is a base-specifier.
In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
In a template-argument-list (14.3); the pattern is a template-argument.
In a dynamic-exception-specification (15.4); the pattern is a type-id.
In an attribute-list (7.6.1); the pattern is an attribute.
In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
In a capture-list (5.1.2); the pattern is a capture.
In a sizeof... expression (5.3.3); the pattern is an identifier.
So basically, as a top-level statement, expansion is not allowed. The rationale behind this? No idea. Most likely they only picked contexts where a seperating comma (,) is part of the grammar; anywhere else you might pick overloaded operator, if user-defined types are involved and get in trouble.