Can this not be done with variadic template parameter packs? [duplicate] - c++

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.

Related

Is auto in template parameter list in lambdas part of the standard?

Today, I stumbled across the following code snippet:
#include <utility>
int main()
{
auto a = [](std::pair<auto, auto> value)
{
};
a(std::pair<int, bool>{ 3, true });
}
http://cpp.sh/5p34
I have only one question: is this code supported by the standard?
It compiles in GCC (with -std=c++14), but not clang or Visual Studio 2015 (VC++14).
This seems like it should be part of the standard because if lambdas should have the same template support as regular functions, then this should be supported.
This seems to convert to all template types, not just std::pair.
In C++14, auto is not allowed in template arguments, whether in a lambda or not. Clang and Visual Studio are both right to reject this code.
The C++14 standard reference is [dcl.spec.auto]. The auto specifier is allowed in the following contexts:
In the decl-specifier-seq of a function declarator (e.g., auto f();) (paragraph 2)
In a conversion-function-id (i.e., an operator auto() in a class) (paragraph 2)
In the trailing-return-type of a function declarator (e.g., auto f() -> auto;) (paragraph 2)
In the decl-specifier-seq of a parameter-declaration (as one of the decl-specifiers) of a lambda (paragraph 3); -this is what allows generic lambdas to exist-
In the declaration of a variable at block scope or namespace scope (paragraph 4)
In the declaration of a for loop control variable (paragraph 4), including a range-based for loop (paragraph 5)
In the condition of an if or switch statement or a loop (paragraph 5)
In a new expression, i.e., new auto(42) (paragraph 5)
In the declaration of a static data member in the definition of a class (paragraph 5)
Finally,
A program that uses auto or decltype(auto) in a context not explicitly allowed in this section is ill-formed.
Therefore, auto is not allowed in template parameters, since that case isn't enumerated in [dcl.spec.auto].
I don't know why gcc allows it. It might be related to Concepts Lite, but I don't know if Concepts Lite actually allows this usage. It could just be an unrelated extension that is easy to implement. I assume that
[](std::pair<auto, auto> value) { /* ... */ }
is translated into
struct __some_unique_name {
template <typename T1, typename T2>
auto operator()(std::pair<T1, T2> value) const { /* ... */ }
// ...
};
As far as I can tell this is part of concepts lite and gcc is allowing this as an extension in C++14 similar to the issue in 'auto' not allowed in function prototype with Clang although unlike the previous case using -pedantic does not produce a warning like it should for extensions.
As far as I can tell the most relevant changes from the concepts lite proposal linked above that allow this are to section 7.1.6.4 [dcl.spec.auto] and 8.3.5 [dcl.fct] ; from 7.1.6.4:
Modify paragraph 3 to allow the use of auto within the parameter type of a lambda or function.
If the auto type-specifier appears as one of the decl-specifiers in the decl-specifier-seq of a parameter-declaration in a
parameter type of a lambda-expression, the lambda is a generic lambda
(5.1.2). [ Example:
auto glambda = [](int i, auto a) { return i; }; // OK: a generic lambda
end example ] Similarly, if the auto type-specifier appears in a parameter type of a function declaration, the function declaration
declares an abbreviated function template (8.3.5). [ Example:
void f(const auto&, int); // OK: an abbreviated function template
— end example ]
and from 8.3.5:
Add the following paragraphs after paragraph 15.
An abbreviated function template is a function declaration whose parameter-type-list includes one or more placeholders (7.1.6.4,
7.1.6.5). An abbreviated function template is equivalent to a function template (14.5.6) whose template-parameter-list includes one invented
templateparameter for each occurrence of a placeholder in the
parameter-declaration-clause, in order of appearance. If the
placeholder is designated by the auto type-specifier, then the
corresponding invented template parameter is a type
template-parameter. Otherwise, the placeholder is designated by a
constrained-type-specifier, and the corresponding invented parameter
matches the type and form of the prototype parameter (?) of the
concept designated by the constrainedtype- specifier (14.9.5). The
invented template-parameter is a parameter pack if the corresponding
parameter-declaration declares a function parameter pack and the type
of the parameter contains only one placeholder. If the prototype
parameter of the designated concept declares a template parameter
pack, the corresponding parameter-declaration shall declare a function
parameter pack. The adjusted function parameters of an abbreviated
function template are derived from the parameter-declaration-clause by
replacing each occurrence of a placeholder with the name of the
corresponding invented template-parameter. If the replacement of a
placeholder with the name of a template parameter results in an
invalid parameter declaration, the program is ill-formed. [ Example:
template<typename T> class Vec { };
template<typename T, typename U> class Pair { };
void f1(const auto&, auto);
void f2(Vec<auto*>...);
void f3(auto (auto::*)(auto));
template<typename T, typename U>
void f1(const T&, U); // redeclaration of f1(const auto&, auto)
template<typename... T>
void f2(Vec<T*>...); // redeclaration of f2(Vec<auto*>...)
template<typename T, typename U, typename V>
void f3(T (U::*)(V)); // redeclaration of f3(auto (auto::*)(auto))
[...]

Empty variardic packs of enums -- do they make two functions different?

There is a technique I sometimes use when overriding template functions that goes like this:
#include <utility>
template<int> struct unique_enum { enum class type {}; };
template<int index> using UniqueEnum = typename unique_enum<index>::type;
template<bool b, int index=1>
using EnableFuncIf = typename std::enable_if< b, UniqueEnum<index> >::type;
template<bool b, int index=1>
using DisableFuncIf = EnableFuncIf<!b, -index>;
// boring traits class:
template<typename T>
struct is_int : std::false_type {};
template<>
struct is_int<int> : std::true_type {};
#include <iostream>
// use empty variardic packs to give these two SFINAE functions different signatures:
template<typename C, EnableFuncIf< is_int<C>::value >...>
void do_stuff() {
std::cout << "int!\n";
}
template<typename C, DisableFuncIf< is_int<C>::value >...>
void do_stuff() {
std::cout << "not int!\n";
}
int main() {
do_stuff<int>();
do_stuff<double>();
}
This distinguishes do_stuff from do_stuff, because one takes 0 or more UniqueEnum<1>s, and the other takes 0 or more UniqueEnum<-1>s. gcc 4.8 considers these different empty packs to be distinct.
However, in the latest version of clang I tried, this fails: it treats the function with 0 UniqueEnum<1>s as being the same as the function with 0 UniqueEnum<-1>s.
There are easy workarounds that work in clang, but I'm wondering if my above technique is legal -- do two function templates, which differ only by empty variardic parameter packs, actually different?
I think GCC is right, and your technique is correct. Basically, since the type argument for C is specified explicitly, the question is whether:
a. substitution of C everywhere else in the function template signature happens first, and then type deduction is performed (which should result in a substitution failure); or
b. type deduction is performed first, and then substitution is performed (which would not result in a substitution failure, because the corresponding argument pack would be empty, and so there would be no substitution to perform).
It seems GCC assumes (1), while Clang assumes (2). Paragraph 14.8.2/2 of the C++11 Standard specifies:
When an explicit template argument list is specified, the template arguments must be compatible with the
template parameter list and must result in a valid function type as described below; otherwise type deduction
fails. Specifically, the following steps are performed when evaluating an explicitly specified template
argument list with respect to a given function template:
— The specified template arguments must match the template parameters in kind (i.e., type, non-type,
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. Otherwise,
type deduction fails.
— Non-type arguments must match the types of the corresponding non-type template parameters, or must
be convertible to the types of the corresponding non-type parameters as specified in 14.3.2, otherwise
type deduction fails.
— The specified template argument values are substituted for the corresponding template parameters as
specified below.
The following paragraph then says:
After this substitution is performed, the function parameter type adjustments described in 8.3.5 are performed. [...]
Moreover, paragraph 14.8.2/5 specifies:
The resulting substituted and adjusted function type is used as the type of the function template for template
argument deduction. [...]
Finally, paragraph 14.8.2/6 goes as follows:
At certain points in the template argument deduction process it is necessary to take a function type that
makes use of template parameters and replace those template parameters with the corresponding template
arguments. This is done at the beginning of template argument deduction when any explicitly specified template
arguments are substituted into the function type, and again at the end of template argument deduction
when any template arguments that were deduced or obtained from default arguments are substituted.
This all seems to imply that first substitution is performed, then template argument deduction. Hence, a substitution failure should occur in either case and one of the two templates should be discarded from the overload set.
Unfortunately, there does not seem to be a clear specification as to what the behavior should be when templates arguments are deduced rather than being explicitly specified.

Can a parameter pack be captured implicitly within C++11 lambdas?

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.

Is a heterogeneous variadic non-type template argument count flexible?

The following defines a variadic non-type nested class template, DEF. The non-type template parameters may be heterogeneous according to the type arguments provided for Ts.
template <typename ...Ts>
struct ABC {
template <Ts ...Xs>
struct DEF {};
};
A DEF object can be declared as follows:
ABC<int,bool>::DEF<17,true> x;
My question is, can the number of non-type template arguments provided to DEF be less than the number of type template arguments provided to ABC? For example, are either of these declarations valid:
ABC<int,bool>::DEF<17> y;
ABC<int,bool>::DEF< > z;
No, because Ts... is a pack-expansion.
§14.5.3 [temp.variadic]
p4 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 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;
[...]
p6 The instantiation of a pack expansion [...] produces a list E1, E2, ..., EN, where N is the number of elements in the pack expansion parameters. [...]
So both your examples would be ill-formed, since DEF will take exactly <int, bool>.

Expression contains unexpanded parameter packs

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.