A pack alias can be expanded with the "..." operator in the same way as an unexanded parameter pack of a variadic template.
The most basic (and useful) templated pack alias would be the following, which allow creating packs on the fly:
template <class... T>
using... type_pack = T;
Usage example:
using... my_types = type_pack<int, char, double>;
using my_tuple = tuple<my_types...>;
Variable packs are the same as function parameter packs, except that they can be declared anywhere a variable can be declared. There are two ways to declare and initialize a variable pack.
The first one is by using an already defined type pack. If constructor arguments are specified as single values, all the constructors of the variables in the pack are called with those same arguments.
using... types = type_pack<int, char, double>;
types values1;
type_pack<int, char, double> values2;
types values3 = 10; // all 3 variables are initialized to 10
types values4(10); // same
types values5 = values2;
type_pack<double, double, double> values6 = values2;
//type_pack<int, char> values7 = values2; //error: mismatching pack sizes
template <class... T>
class C
{
T... values;
};
template <class... T>
void f(T... t)
{
T... t2 = t * 2;
}//double... values8; //error: unknown pack size
//double... values9 = 10.0; //error: unknown pack size
double... values10 = values2; // pack of 3 variables of type double
double... values11(values2); // same
auto... values12 = values2; // pack of 3 variables of types int, char and double
template <class... T>
void g(T... t)
{
auto... t2 = t * 2;
}
Operations can be done on packs if all the unexpanded packs are of the same size.
values10 = values11 * 2.0;
values11 += values10;
values11 = values10 + values11;
Single values can be used along with pack but only in operations in which those single values are const.
double single_value = 1.0;
values10 *= single_value; //ok: rhs is const single_value
single_value *= values10; //error: lhs of operator*= cannot be const
void h(const double&, double&) {}
h(values10, values11); //ok: 2 packs of the same size (h will be called 3 times)
h(single_value, values10); //ok: first arg of h is const (h will be called 3 times, each time with the same single_value)
h(values10, single_value); //error: trying to pass a non-const single value in an unexpanded context
Variable template pack
Variable packs should also work for variable templates.
The most basic (and useful) variable template packs would be an equivalent to std::integer_sequence that would allow working directly with an integer pack (instead of having to store that pack in a type).
template <class T, T... I>
constexpr T... integer_pack = I;
template <size_t... I>
constexpr size_t... index_pack = I;
template <class T, T N>
constexpr T... make_integer_pack = integer_pack<T, /* a sequence 0, 1, 2, ..., N-1 */>;
template <size_t N>
constexpr size_t... make_index_pack = make_integer_pack<size_t, N>;
template <class... T>
constexpr size_t... index_pack_for = make_index_pack<sizeof...(T)>;With these variable template packs, additional utilies can be provided for tuple-like classes:
// tuple_indices<T> is equivalenent to 0, 1, ..., tuple_size<T>::value - 1
template <class T>
constexpr size_t... tuple_indices = make_index_pack<tuple_size<T>::value>;
// tuple_elements<T> is equivalent to tuple_element_t<0, T>, tuple_element_t<1, T>, ..., tuple_element_t<N-1, T>
template <class T>
using... tuple_elements = tuple_element_t<tuple_indices<T>, T>;
These are much more simpler to use than current workarounds requiring usage of std::integer_sequence. Example of passing all elements of tuple t as arguments to a function f:
template <class... Args>
void f(Args&&... args);
template <class Tuple>
void g(T&& t)
{
f(get<tuple_indices<T>>(forward<T>(t))...);
}
Returning a pack from a function
With pack variables comes the need for functions returning a pack of values.
The most basic function that could be defined is one returning a pack from several values:
template <class... T>
decltype(auto)... make_pack(T&&... t) { return forward<T>(t); }void g(int a, int b, int c, int d, int e, int f, int g, int h);
// Following functions forward all arguments to g by first multiplying them by 2 and adding 3
void f(int a, int b, int c, int d, int e, int f, int g, int h)
{
// Old way
g(a*2 + 3, b*2 + 3, c*2 + 3, d*2 + 3, e*2 + 3, f*2 + 3, g*2 + 3, h*2 + 3);
// New way
g(make_pack(a, b, c, d, e, f, g, h)*2 + 3 ...);
}Another usage would be a function returning all the values of a tuple-like class:
// get_all(t) is equivalent to get<0>(t), get<1>(t), ..., get<N-1>(t)
template <class T>
constexpr decltype(auto)... get_all(T&& t) { return get<tuple_indices<T>>(forward<T>(t)); }template <class... T>
void f(T&&... t);
template <class Tuple>
void g(T&& t)
{
f(get_all(forward<T>(t))...);
}
Expanding nested pack aliases
Considering the following:
using... original = type_pack<T0, T1, /*...*/, TN>;
template <class... T>
using... alias = /*...*/;
using... a = alias<original...>;
using... b = alias<original>...;
The pack a is the result of applying the alias on the entire original pack.
The pack b is the result of applying the alias on each individual elements of the original pack.
using... a = alias<T0, T1, /*...*/, TN>;
using... b = type_pack<alias<T0>..., alias<T1>..., /*...*/, alias<TN>...>;using... orig = type_pack<T0, T1, /*...*/, TN>;
template <class... T>
using... a1 = /*...*/;
template <class... T>
using... a2 = /*...*/;
using... a = a1<a2<orig...>...>;
// same as: using... temp = a2<orig...>;
// using... a = a1<temp...>;
using... b = a1<a2<orig>... ...>;
// same as: using... temp = a2<orig>...;
// using... b = a1<temp...>;
using... c = a1<a2<orig...>>...;
// same as: using... temp = a2<orig...>;
// using... c = a1<temp>...;
using... d = a1<a2<orig>...>...;
// same as: using... temp = a2<orig>...;
// using... d = a1<temp>...;
//using... e = a1<a2<orig>>... ...;
// error
//using... f = a1<a2<orig... ...>>;
// error
Additional examples
struct A {};
struct B {};
template <class... T>
using... twice = type_pack<T..., T...>;
using... AB = type_pack<A, B>; // A, B
using... t1 = twice<AB...>; // A, B, A, B
using... t2 = type_pack<twice<AB...>...>; // A, B, A, B
using... t3 = twice<AB>...; // A, A, B, B
using... t4 = type_pack<twice<AB>... ...>; // A, A, B, B
using... t5 = twice<twice<AB...>...>; // A, B, A, B, A, B, A, B
using... t6 = twice<twice<AB>... ...>; // A, A, B, B, A, A, B, B
using... t7 = twice<twice<AB...>>...; // A, A, B, B, A, A, B, B
using... t8 = twice<twice<AB>...>...; // A, A, A, A, B, B, B, B
struct C {};
template <class... T>
using... add_c = type_pack<T..., C>;
using... u1 = add_c<AB...>; // A, B, C
using... u2 = add_c<AB>...; // A, C, B, C
using... u3 = add_c<add_c<AB...>...>; // A, B, C, C
using... u4 = add_c<add_c<AB>... ...>; // A, C, B, C, C
using... u5 = add_c<add_c<AB...>>...; // A, C, B, C, C, C
using... u6 = add_c<add_c<AB>...>...; // A, C, C, C, B, C, C, C
using... v1 = add_c<twice<AB...>...>; // A, B, A, B, C
using... v2 = add_c<twice<AB>... ...>; // A, A, B, B, C
using... v3 = add_c<twice<AB...>>...; // A, C, B, C, A, C, B, C
using... v4 = add_c<twice<AB>...>...; // A, C, A, C, B, C, B, C
using... w1 = twice<add_c<AB...>...>; // A, B, C, A, B, C
using... w2 = twice<add_c<AB>... ...>; // A, C, B, C, A, C, B, C
using... w3 = twice<add_c<AB...>>...; // A, A, B, B, C, C
using... w4 = twice<add_c<AB>...>...; // A, A, C, C, B, B, C, C
Hi,I have been working on several ideas that would fill several gaps and make things much easier when dealing with parameter packs. Before going in with a formal proposal, I first wanted to share my ideas. Feedback would be greatly appreciated.Type pack aliasA pack alias (templated or not) can be defined with a "using..." directive. On the right hand side must be an unexpanded type pack. This unexpanded type pack can come from an unexpanded parameter pack of a variadic template or from another pack alias.A pack alias can be expanded with the "..." operator in the same way as an unexanded parameter pack of a variadic template.
The most basic (and useful) templated pack alias would be the following, which allow creating packs on the fly:
template <class... T>
using... type_pack = T;
Usage example:
using... my_types = type_pack<int, char, double>;
using my_tuple = tuple<my_types...>;
Variable packVariable packs are the same as function parameter packs, except that they can be declared anywhere a variable can be declared. There are two ways to declare and initialize a variable pack.
The first one is by using an already defined type pack. If constructor arguments are specified as single values, all the constructors of the variables in the pack are called with those same arguments.
using... types = type_pack<int, char, double>;
types values1;
type_pack<int, char, double> values2;
types values3 = 10; // all 3 variables are initialized to 10
types values4(10); // same
types values5 = values2;
type_pack<double, double, double> values6 = values2;
//type_pack<int, char> values7 = values2; //error: mismatching pack sizes
template <class... T>
class C
{
T... values;
};
template <class... T>
void f(T... t)
{
T... t2 = t * 2;
}A variable pack can also be declared without a defined type pack. In that case, it must be initialized from another variable pack.//double... values8; //error: unknown pack size
//double... values9 = 10.0; //error: unknown pack size
double... values10 = values2; // pack of 3 variables of type double
double... values11(values2); // same
auto... values12 = values2; // pack of 3 variables of types int, char and double
template <class... T>
void g(T... t)
{
auto... t2 = t * 2;
}
Operations can be done on packs if all the unexpanded packs are of the same size.values10 = values11 * 2.0;
values11 += values10;
values11 = values10 + values11;
Single values can be used along with pack but only in operations in which those single values are const.
double single_value = 1.0;
values10 *= single_value; //ok: rhs is const single_value
single_value *= values10; //error: lhs of operator*= cannot be const
void h(const double&, double&) {}
h(values10, values11); //ok: 2 packs of the same size (h will be called 3 times)
h(single_value, values10); //ok: first arg of h is const (h will be called 3 times, each time with the same single_value)
h(values10, single_value); //error: trying to pass a non-const single value in an unexpanded context
Variable template pack
Variable packs should also work for variable templates.
The most basic (and useful) variable template packs would be an equivalent to std::integer_sequence that would allow working directly with an integer pack (instead of having to store that pack in a type).
template <class T, T... I>
constexpr T... integer_pack = I;
template <size_t... I>
constexpr size_t... index_pack = I;
template <class T, T N>
constexpr T... make_integer_pack = integer_pack<T, /* a sequence 0, 1, 2, ..., N-1 */>;
template <size_t N>
constexpr size_t... make_index_pack = make_integer_pack<size_t, N>;
template <class... T>
constexpr size_t... index_pack_for = make_index_pack<sizeof...(T)>;With these variable template packs, additional utilies can be provided for tuple-like classes:
// tuple_indices<T> is equivalenent to 0, 1, ..., tuple_size<T>::value - 1
template <class T>
constexpr size_t... tuple_indices = make_index_pack<tuple_size<T>::value>;
// tuple_elements<T> is equivalent to tuple_element_t<0, T>, tuple_element_t<1, T>, ..., tuple_element_t<N-1, T>
template <class T>
using... tuple_elements = tuple_element_t<tuple_indices<T>, T>;
These are much more simpler to use than current workarounds requiring usage of std::integer_sequence. Example of passing all elements of tuple t as arguments to a function f:template <class... Args>
void f(Args&&... args);
template <class Tuple>
void g(T&& t)
{
f(get<tuple_indices<T>>(forward<T>(t))...);
}
Returning a pack from a function
With pack variables comes the need for functions returning a pack of values.
The most basic function that could be defined is one returning a pack from several values:
template <class... T>
decltype(auto)... make_pack(T&&... t) { return forward<T>(t); }Usage example:void g(int a, int b, int c, int d, int e, int f, int g, int h);
// Following functions forward all arguments to g by first multiplying them by 2 and adding 3
void f(int a, int b, int c, int d, int e, int f, int g, int h)
{
// Old way
g(a*2 + 3, b*2 + 3, c*2 + 3, d*2 + 3, e*2 + 3, f*2 + 3, g*2 + 3, h*2 + 3);
// New way
g(make_pack(a, b, c, d, e, f, g, h)*2 + 3 ...);
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Usage example:
using... my_types = type_pack<int, char, double>;
using my_tuple = tuple<my_types...>;This immediately creates a problem. One of the properties of packs today is that it is possible to syntactically determine whether some snippet of C++ syntax contains unexpanded parameter packs, and to determine which packs it contains. This is crucial to some implementation techniques.It's also important in resolving some grammar ambiguities. Today, if I have:template<typename T> void f(typename T::type x ...);I know this is a vararg function. With your proposal, I could presumably write:template<typename...Ts> struct X {using ...type = Ts;};void g() {f<X<int, int, int>>(1, 2, 3);}and 'f' would mean something fundamentally different.
I had a similar-but-different proposal a while back (which I've not written up for committee consideration yet) which avoids this problem by using a slightly different syntax:template<typename...Ts> struct types {using ... = Ts; // "...types<Ts>" is the pack "Ts"};typedef std::types<int, char, double> icd; // just a normal typevoid f(...icd ...x) // "...icd" is a pack of types; this is "void f(int, char, double)" except that we have a pack of parameters
using... types = type_pack<int, char, double>;
types values1;This syntax doesn't work. Consider:template<typename ...Ts>void f(Ts ...ts) {g(ts + []() {types values;return values;}() ...);}This would be ambiguous: does each lambda declare a *pack* of values, or a *slice* of that pack of values? The way to fix this is to consistently use pack expansion whenever you expand a pack:// assuming 'types' names a pack of typestypes ...values1; // ok, declare a pack of values named 'values1'.
template <class... T>
class C
{
T... values;This, for instance, is a problem, because X::values (for a dependent type X) might or might not be a pack if this were valid.
auto... values12 = values2; // pack of 3 variables of types int, char and doublePresumably this means that your expansion model is that this expands toauto values12$0 = values2$0;auto values12$1 = values2$1;auto vaules12$2 = values2$2;not the more natural choice ofauto values12$0 = values2$0, values12$1 = values2$1, vaules12$2 = values2$2;(which would be ill-formed, because the 'auto' deduces to different types in different deductions).
Operations can be done on packs if all the unexpanded packs are of the same size.
values10 = values11 * 2.0;
values11 += values10;
values11 = values10 + values11;Again, this doesn't work, due to ambiguity over where the expansion happens. Instead, I'd suggest (and indeed, have been intending to propose for standardization) adding an optional '...' before the ';' in an expression-statement, to represent a pack expansion. So you'd write:values10 = values11 * 2.0 ...;
template <class... Args>
void f(Args&&... args);
template <class Tuple>
void g(T&& t)
{
f(get<tuple_indices<T>>(forward<T>(t))...);
}This is still a little unclean. My proposal supports this as:f(...std::forward<T>(t) ...);
Returning a pack from a function
With pack variables comes the need for functions returning a pack of values.
Why not return a tuple? Packs are not types, so functions returning packs doesn't really seem to make much sense.
void g(int a, int b, int c, int d, int e, int f, int g, int h);
// Following functions forward all arguments to g by first multiplying them by 2 and adding 3
void f(int a, int b, int c, int d, int e, int f, int g, int h)
{
// Old way
g(a*2 + 3, b*2 + 3, c*2 + 3, d*2 + 3, e*2 + 3, f*2 + 3, g*2 + 3, h*2 + 3);
// New way
g(make_pack(a, b, c, d, e, f, g, h)*2 + 3 ...);Again, this has the problem of not syntactically identifying what's a pack and what's not. With my syntax:g(...tie(a, b, c, d, e, f, g, h)*2 + 3...);
Hi Richard,Thanks for you time; you bring a lot of good points. I just took a look at the discussion you linked. I have to say that overloading operator... like you propose is indeed really interesting. It is really neat and doesn't have some of the issues introduced by my proposal. However, if I can manage to solve those issues, I think (from my point of view) that my proposal would provide a bit more power and fill more gaps in the language. See below my answers to the issues you raised.Usage example:
using... my_types = type_pack<int, char, double>;
using my_tuple = tuple<my_types...>;This immediately creates a problem. One of the properties of packs today is that it is possible to syntactically determine whether some snippet of C++ syntax contains unexpanded parameter packs, and to determine which packs it contains. This is crucial to some implementation techniques.It's also important in resolving some grammar ambiguities. Today, if I have:template<typename T> void f(typename T::type x ...);I know this is a vararg function. With your proposal, I could presumably write:template<typename...Ts> struct X {using ...type = Ts;};void g() {f<X<int, int, int>>(1, 2, 3);}and 'f' would mean something fundamentally different.First, the ellipsis should go after the type name, not the var name.
So with my proposal, f would be declared like this:template<typename T> void f(typename T::type... x);Secondly, you are right that it remains ambiguous if T::type is a pack or not. I guess a proper solution would be to have the "typename..." keyword (used in the same context/fashion as regular "typename"). The example would become:template<typename T> void f(typename... T::type... x);So first ellipsis (after typename) would indicate that what follows is a pack (of types) and second ellipsis is for pack expansion.Note that the same problem exists for a pack of values as you point out later (see my answer to this).
auto... values12 = values2; // pack of 3 variables of types int, char and doublePresumably this means that your expansion model is that this expands toauto values12$0 = values2$0;auto values12$1 = values2$1;auto vaules12$2 = values2$2;not the more natural choice ofauto values12$0 = values2$0, values12$1 = values2$1, vaules12$2 = values2$2;(which would be ill-formed, because the 'auto' deduces to different types in different deductions).I didn't think about this. Indeed, as you say, for declaring packs it sounds better to expand in the "less natural" way that you described (but I don't like calling it less natural).Operations can be done on packs if all the unexpanded packs are of the same size.
values10 = values11 * 2.0;
values11 += values10;
values11 = values10 + values11;Again, this doesn't work, due to ambiguity over where the expansion happens. Instead, I'd suggest (and indeed, have been intending to propose for standardization) adding an optional '...' before the ';' in an expression-statement, to represent a pack expansion. So you'd write:values10 = values11 * 2.0 ...;That sounds like a good idea that would work with the current proposal.I wonder if it would be required also when declaring :1) auto... values12 = values2;vs2) auto... values12 = values2 ...;
Unless I'm wrong, (2) is invalid because the first ellipsis is already expanding the pack.
template <class... Args>
void f(Args&&... args);
template <class Tuple>
void g(T&& t)
{
f(get<tuple_indices<T>>(forward<T>(t))...);
}This is still a little unclean. My proposal supports this as:f(...std::forward<T>(t) ...);See my "cleaner" example a bit further:f(get_all(forward<T>(t))...);
I've got to admit that in this case it's not as clean as your solution, but still it's not that bad.Change the global get_all by a member function and it also gets nicer:f(forward<T>(t).all()...);
Returning a pack from a function
With pack variables comes the need for functions returning a pack of values.
Why not return a tuple? Packs are not types, so functions returning packs doesn't really seem to make much sense.In your proposal, isn't the operator...() function returning a pack? It looks like we have the same problem to solve.
So far I can think of two expansion models for returning packs. I'm not sure which one is the best:1- Calling a function returning a pack would in fact call several functions, one for each pack element (so functions make_pack$0, make_pack$1, etc would be called). The biggest problem with this approach is that the code in the function is called several times... which might not always be clear to the user. I think this approach is also considered in the thread you linked where those issues are raised.2- Have the language manage those multiple return values by itself. Maybe internally the compiler can translate it to/from a tuple-like struct, with a bit of "magic". But I'm not an expert in this area so couldn't say. It might be a lot more trickier to implement (if possible at all) but would lead to a much cleaner behavior from the user perspective.
void g(int a, int b, int c, int d, int e, int f, int g, int h);
// Following functions forward all arguments to g by first multiplying them by 2 and adding 3
void f(int a, int b, int c, int d, int e, int f, int g, int h)
{
// Old way
g(a*2 + 3, b*2 + 3, c*2 + 3, d*2 + 3, e*2 + 3, f*2 + 3, g*2 + 3, h*2 + 3);
// New way
g(make_pack(a, b, c, d, e, f, g, h)*2 + 3 ...);Again, this has the problem of not syntactically identifying what's a pack and what's not. With my syntax:g(...tie(a, b, c, d, e, f, g, h)*2 + 3...);Isn't the make_pack declaration explicit enough that what is returned by it is a pack?
Sorry, the example I meant wastemplate<typename T> void f(typename T::type ...);
I think it'd be unfortunate to need to introduce another disambiguation syntax (in addition to 'template' and 'typename').
Since different elements of a nested-name-specifier may or may not be packs, I think a better place for the ... would be after a ::. So:typename T::...U::type // T::U is a packtypename T::U::...type // T::U::type is a packT::...value // T::value is a pack
This second approach is very problematic, and actually doesn't really make sense from the user's perspective, nor from the mechanical perspective of pack expansion. Consider:
template<typename...T> using ...types = T;types<int, char, double> f() { // return a packstd::cout << "hello, world\n";return {0, 0, 0};}template<typename...T> void g(T...);template<typename...T> void h(T ...t) {h(t ? f() : 0 ...);}void i() { h(true, false, true); }How many times is f() called here? It seems "obvious" to me that it should get called once per pack expansion, just like any other code within a pack expansion, and the message should be printed out twice. It's not really clear that it'd be possible to support this kind of construct with the "language magic" in approach 2 (where the message would somehow be printed out once?).
With f() returning a tuple, local variable packs, and explicit packification, we get to write three different meaningful programs:template<typename...T> void h1(T ...t) {// call 'f()' once for each element of 't' that is true, and use the corresponding element of f()'s resulth(t ? ...f() : 0 ...);}template<typename...T> void h2(T ...t) {// call 'f()' three times, put each slice of 'f' into a local variable...auto ...elems = ...f();// ... and pass in the elements for which the corresponding element of 't' is trueh(t ? elems : 0 ...);}template<typename...T> void h3(T ...t) {// call 'f()' once...auto elems = f();// ... and pass in the elements for which the corresponding element of 't' is trueh(t ? ...elems : 0 ...);}
template <class... T>using... twice = types<T..., T...>;using... AB = types<A, B>;using... ABAB = twice<AB...>; // A, B, A, Busing... AABB = twice<AB>...; // A, A, B, B
template <class... T>using twice = types<T..., T...>;using AB = types<A, B>;using ABAB = twice<...AB...>;using AABB = ???;
constexpr static int... others = Others;
constexpr static int others$0 = Others$0;constexpr static int others$1 = Others$1;
[...]
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/YkHPCYb-KPQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
Sorry, the example I meant wastemplate<typename T> void f(typename T::type ...);With the following syntax, there shouldn't be ambiguity:template <typename T> void f(typename T::...type ...);(similar to the following, which is not ambiguous:)template <typename... T> void f(T ...);
I think it'd be unfortunate to need to introduce another disambiguation syntax (in addition to 'template' and 'typename').
Well, that's what your proposal do (indirectly) as well. With your proposal, you have to prefix with an ellipsis everywhere. With mine, you only prefix with an ellipsis in ambiguous contexts.
Also, I'm not sure I understand that third example. What is the type of elems?
I also have a question regarding your proposal. In the last part of my original post, I proposed 2 different ways to expand a template pack alias. It would allow the following (see my original post for more detailed examples):
template <class... T>using... twice = types<T..., T...>;using... AB = types<A, B>;using... ABAB = twice<AB...>; // A, B, A, Busing... AABB = twice<AB>...; // A, A, B, BWould the last line be possible with your proposal?template <class... T>using twice = types<T..., T...>;using AB = types<A, B>;using ABAB = twice<...AB...>;using AABB = ???;
The example assumes that we have both my proposal and local variable packs (which are something which I think we both agree are desirable, and which seem to not have any practical or theoretical problems).
How do you write concat in your proposal?
template <class... T> using... types = T;`using... packA = /*...*/;using... packB = /*...*/;using... concatenated = types<packA..., packB...>;
I think you'd need a pack-of-packs as a template parameter:template<typename ... ...T> using ...concat = ... ...T ... ...;right?
const std::string &to_string(const std::string &str) { return str; }
void do_concat(std::string&) {}
template<typename... rest_t>
void do_concat(std::string &str,const std::string &next,const rest_t&... rest) {
str+=next;
do_concat(str,rest...);
}
template<typename... args_t>
std::string make_string(const args_t&... args) {
using std::to_string;
using ::to_string;
std::string str;
do_concat(str,to_string(args)...);
return str;
}
int main()
{
std::cout << make_string("aaa ",1," bbb ",1.3) << "\n";
}
template<typename... args_t>
std::string make_string(const args_t&... args) {
using std::to_string;
using ::to_string;
std::string str;
(str+=to_string(args))...;
return str;
}
template<typename... args_t> do_nothing(const args_t&... args) {}
template<typename... args_t>
std::string make_string(const args_t&... args) {
using std::to_string;
using ::to_string;
std::string str;
do_nothing((str+=to_string(args))...);
return str;
}
template <class... Bases>class Derived : public Bases...{};
template <int... Values>void f(){int values[] = { Values... };}
do_nothing{(str += to_string(args))...};
do_nothing{[&]() {str += to_string(args);str += separator;}() ...};
for... (const auto& a : args) {str += to_string(a);str += separator;}
for (const auto& a : { to_string(args)... }) {str += a;str += separator;}
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/YkHPCYb-KPQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
I guess the reason is to initially make it simple and uniform to expand a pack. Right now, the equivalent of a pack expansion is as simple as separating each elements of the pack by a comma and nothing else. Note that current rules do not only allow to expand in function call but in other contexts as well.Here is one example:template <class... Bases>class Derived : public Bases...{};And another one:template <int... Values>void f(){int values[] = { Values... };}As you can see, all current use cases are expanded by adding a comma between each expanded elements.One of the things Richard suggested in this thread was adding a new meaning to the unpack operator when it is followed by a semicolon. It would be expanded into several expression statement.
Now about the problem you are trying to solve with the current standard, just convert your 'do_nothing' function to a class instead and call its constructor using list-initialization:do_nothing{(str += to_string(args))...};
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
namespace detail{
// array alias, since GCC doesn't correctly support LTR evaluation for structs / classes
using swallow = int[];
} // detail::
// extra 0 at front since 0-sized arrays aren't permitted
// void() in the middle to suppress overloaded comma-operators
// 0 at end to get a uniform type
#define VARIADIC_EXPAND(expr) detail::swallow{ 0, ((expr), void(), 0)... }
VARIADIC_EXPAND( str += to_string(args) );
template <int... I>struct int_seq{static decltype(I) operator...() { return I; }};using seq = int_seq<0, 1, 2, 3, 4>;using seq2 = int_seq<...seq * 2 ...>; // 0, 2, 4, 6, 8
struct A { using Y = int; };
struct B { using Y = char; };
struct X {using ... = ...types<A, B>;struct Y {
using ... = ...types<float, double>;};};void f(...X::Y...);
void f(int, char);
void f(float, double);
Hi Richard,I am thinking a bit more about your proposal and have a few questions.1.Consider the following code:template <class... T>struct A{using data_types = types<T...>;T operator...() const;
/*...*/};
template <class AType> // AType is supposed to be a type with the same interface as Astruct B{using data_types = types<typename ...AType::data_types...>;std::tuple<...data_types...> _data;B(...data_types... d) : _data(d...) {}B(const AType& a) : B(...a...) {}};Q: Is the underlined typename required in your proposal? Or does the ellipsis prefix removes the need for it in that context?(also please feel free to correct any other mistake I may have made)
2.(answer to this one might alter answer to first)Did you think about static operator... ?template <int... I>struct int_seq{static decltype(I) operator...() { return I; }};using seq = int_seq<0, 1, 2, 3, 4>;using seq2 = int_seq<...seq * 2 ...>; // 0, 2, 4, 6, 8If this is allowed, this would make typename in my first example required.
3.You propose two (three?) packification syntax:- using ... =- operator...- (static operator...)Would a class be allowed to define more than one of these?
4.Consider the following code:struct A { using Y = int; };struct B { using Y = char; };struct X {using ... = ...types<A, B>;struct Y {
using ... = ...types<float, double>;};};void f(...X::Y...);The definition of f looks ambiguous to me; it is not clear if the ... prefix applies to X or Y. Is it equivalent tovoid f(int, char);orvoid f(float, double);?
The 'typename' would be required here, to indicate that the complete qualified-id names a type (pack). Consider:
...AType::static_tuple_data_member...I'd previously been expecting the syntax for the above to be:...typename AType::data_types...for consistency with ... as a prefix operator on values, but you raise an excellent point (implied here and made explicit in your question 4): we could potentially want to turn any element of a nested name specifier into a pack. That being the case, one possible way of writing this would be:typename AType::...data_typesHowever, that introduces an ambiguity in the expression case. Is:...AType::static_tuple_data_member...
1) (...A)::b2) ...(A::b) // or simply:
...A::b
3) typename (...A)::B4) typename ...(A::B) // error
typename ...A::B // ok
5) (...a).b6) ...(a.b) // or simply:
...a.b
constexpr auto seq = int_seq<0, 1, 2, 3, 4>();constexpr auto seq2 = int_seq<...seq * 2 ...>();... or ...using seq = int_seq<0, 1, 2, 3, 4>;using seq2 = int_seq<...seq{} * 2 ...>;... seem nearly-as-good syntaxes for the same thing.
Yes. "using ... = " would specify what happens when prefix ..." is applied to the type, "operator..." would specify what happens when prefix "..." is applied to a value of that type. I don't see a need for a restriction here, since I don't think there is any ambiguity between these two.
struct A {using ... = ...types<int, long>;using Ret = types<float, double>;...Ret operator...() const { return (...Ret)1; }};f(...A{}...);
f(0, 0L);
f(1.0f, 1.0);
f(...A{}...); // ==> f(1.0f, 1.0);f((...A){}...); // ==> f(0, 0L);
4.Consider the following code:struct A { using Y = int; };struct B { using Y = char; };struct X {using ... = ...types<A, B>;struct Y {
using ... = ...types<float, double>;};};void f(...X::Y...);The definition of f looks ambiguous to me; it is not clear if the ... prefix applies to X or Y. Is it equivalent tovoid f(int, char);orvoid f(float, double);?That's a great question. I was intending for it to mean the latter, but that makes it really hard to express the former.
void f(decltype(declval<...X>())::Y...);
template <class T>using identity = T;
void f(identity<...X>::Y...);