Pack expansion: blocks, switch/case

1,233 views
Skip to first unread message

smili...@googlemail.com

unread,
Jul 5, 2016, 9:28:08 AM7/5/16
to ISO C++ Standard - Future Proposals
Hello together, 

during development of my "Compile-time trie"-based string matcher, I wanted to use the compiler's advanced code generation for switch statements (instead of non-optimized recursion/unrolling), but had to use preprocessor based techniques (cttrie_sw256-boost.tcc, cttrie_sw32-boost.tcc), due to the lack of an appropriate pack-expansion.
And because the set of generated case-values is mostly sparse, using a table-based technique (i.e. generate an array of function pointers via pack expansion) was also not adequate.
A short web search also brought up some StackOverflow questions asking for switch/case with parameter packs.

A first syntax idea was:

  template <typename... Transitions> auto Switch(TrieNode<Transitions...> ...) {
   
// ...
   
switch (ch) {
     
case 0: ... special code ... break;

     
case (Transitions::Char) {  // Note: no ":"   // the label has to be constexpr (nothing new)
       
return checkTrie(Transitions::Next(),str,...); }...

   
}
   
// return error;
 
}

But while reviewing some possible/desired features:
  • allow mix of pack-expanded and regular case statements inside a single switch
  • break / continue / [[fallthrough]]  should work as usual   (this rules out lambda-based approaches)
  • goto into the case body can't work (duplicate label)
  • default label only possible the "old way", not via pack expansion.
  • introduces Block scope??  (-> variable lifetime...)
I found the a more general solution: A pack-expansion for Blocks, e.g.

template <int... Is> void function(...)
{
 
// ...some normal code
     
 
{
    f
(Is);
   
// more block code, probably using Is - or other packs of same length
 
}...

 
// ...some more normal code
}

which simplifies code where, up until now, techniques like
// helper to execute('map') parameter pack:  pass({(f(args),0)...});
inline void pass(const std::initializer_list<int> &) {}
had to be used.

Example with switch/case:

template <typename... Transitions> auto Switch(TrieNode<Transitions...> ...)
 
// ...
 
switch (ch) {
   
{ case Transitions::Char:
     
return checkTrie(Transitions::Next(),...); }...
 
}
 
// ...

This requires almost no changes to the C++ syntax, because a switch-block is not very different from other blocks (even permitting constructs like Duff's device).

  Tobias

inkwizyt...@gmail.com

unread,
Jul 5, 2016, 9:43:26 AM7/5/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com

smili...@googlemail.com

unread,
Jul 5, 2016, 1:02:58 PM7/5/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
I'm well aware of existing expression-expansion ideas - but these (probably) won't help with the switch statement.
Even more, if we have block/statement-expansion, you can easily use it in an expression context by wrapping it in an immediately executed lambda.

But what I don't quite understand: Given the general interest in such a feature (for which you gave some more examples), why do all these proposals seem to go nowhere?

Adding Block/Statement expansion seem quite easy - both the implementation and the specification. At least, I haven't heard of any substantial issues in those areas.

I'm not talking about anything fancy, just
  {
   
// statements as in a normal block
 
}...
with the explicit allowance for use in conjunction with switch/case (and I've given some the design considerations in my initial mail).

Why not bake such simple basics into the language and consider all the other ideas only as a second step?

At least, I'd be helpful to have SOME guidance from the standardization body, whether they prefer such a simple solution or whether they'd like more work done on "for constexpr"(e.g.). 
And, if block/statement expansion won't move forward (for now), maybe add a more specific expansion for switch/case (something akin to the "first syntax idea" from my first mail)?

  Tobias

inkwizyt...@gmail.com

unread,
Jul 5, 2016, 3:07:52 PM7/5/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com


On Tuesday, July 5, 2016 at 7:02:58 PM UTC+2, smili...@googlemail.com wrote:
On Tuesday, July 5, 2016 at 3:43:26 PM UTC+2, inkwizyt...@gmail.com wrote:
On Tuesday, July 5, 2016 at 3:28:08 PM UTC+2, smili...@googlemail.com wrote:
Hello together, 

during development of my "Compile-time trie"-based string matcher, I wanted to use the compiler's advanced code generation for switch statements (instead of non-optimized recursion/unrolling), but had to use preprocessor based techniques (cttrie_sw256-boost.tcc, cttrie_sw32-boost.tcc), due to the lack of an appropriate pack-expansion.
And because the set of generated case-values is mostly sparse, using a table-based technique (i.e. generate an array of function pointers via pack expansion) was also not adequate.
A short web search also brought up some StackOverflow questions asking for switch/case with parameter packs.
[...]

This requires almost no changes to the C++ syntax, because a switch-block is not very different from other blocks (even permitting constructs like Duff's device).

  Tobias

I'm well aware of existing expression-expansion ideas - but these (probably) won't help with the switch statement.
Even more, if we have block/statement-expansion, you can easily use it in an expression context by wrapping it in an immediately executed lambda.

But what I don't quite understand: Given the general interest in such a feature (for which you gave some more examples), why do all these proposals seem to go nowhere?


Probably biggest reason is that nobody did needed legworking to push it further.

Primary reason why I post it to prevent repeating same discussion. You can try find new arguments but
most of feedback that you would get is probably same as in that links (this is not full list I only grab couple of last one that I remember).
 
Adding Block/Statement expansion seem quite easy - both the implementation and the specification. At least, I haven't heard of any substantial issues in those areas.

I'm not talking about anything fancy, just
  {
   
// statements as in a normal block
 
}...
with the explicit allowance for use in conjunction with switch/case (and I've given some the design considerations in my initial mail).

Why not bake such simple basics into the language and consider all the other ideas only as a second step?

At least, I'd be helpful to have SOME guidance from the standardization body, whether they prefer such a simple solution or whether they'd like more work done on "for constexpr"(e.g.). 
And, if block/statement expansion won't move forward (for now), maybe add a more specific expansion for switch/case (something akin to the "first syntax idea" from my first mail)?

  Tobias


Is hard for me to answer that because I only user of this public list, but probably best way would be add all alternative version in one paper with all pros/cons and ask for vote on committee meeting what version is most preferred.
I would personally prefer `for constexpr` because with small tweaks it would allow usage outside templates.

Nicol Bolas

unread,
Jul 5, 2016, 9:25:22 PM7/5/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com

Things don't get standardized because someone posts a suggestion in a mailing list. Things get standardized because someone puts work and effort into it, pushing it forward at actual standards meetings.

This ML is useful for figuring out what a proposal ought to be. But you can't treat it as a place where you drop off an idea and hope that someone else does something with it. Well, you can, but if you do, you shouldn't be surprised if your idea never goes anywhere.

Adding Block/Statement expansion seem quite easy - both the implementation and the specification. At least, I haven't heard of any substantial issues in those areas.

Then it shouldn't be difficult for you to prove that. After all, if it's that easy to implement, you ought to be able to fork Clang or GCC and implement it, right? If it's that easy to specify, then you should be able to write the appropriate standards wording for it.

It's easy to say that someone else's job is easy. It's not so easy when you have to do it yourself.

Sergey Vidyuk

unread,
Jul 7, 2016, 2:33:50 AM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com

At least, I'd be helpful to have SOME guidance from the standardization body, whether they prefer such a simple solution or whether they'd like more work done on "for constexpr"(e.g.). 
And, if block/statement expansion won't move forward (for now), maybe add a more specific expansion for switch/case (something akin to the "first syntax idea" from my first mail)?

some info on for constexpr discussed here: https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-proposals/2hdpLnXJKkQ Right now I'm playing with gcc code (using my free time so progress is not rapid enough). I want to have implementation I can play with and share with other people on this list and then start to work on proposal paper.

I'm just maling list reader and have no relation to the standardization body so I would like to know which design goal is better to solve "statement folding" related tasks too.

Sergey

smili...@googlemail.com

unread,
Jul 7, 2016, 8:53:46 AM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
On Wednesday, July 6, 2016 at 3:25:22 AM UTC+2, Nicol Bolas wrote:
Things don't get standardized because someone posts a suggestion in a mailing list. Things get standardized because someone puts work and effort into it, pushing it forward at actual standards meetings.

Sure. But not all of us are in a position to participate in standards meetings.
 
This ML is useful for figuring out what a proposal ought to be. But you can't treat it as a place where you drop off an idea and hope that someone else does something with it. Well, you can, but if you do, you shouldn't be surprised if your idea never goes anywhere.

My actual point was that it's not just me, who would like to see such a feature.
 
Adding Block/Statement expansion seem quite easy - both the implementation and the specification. At least, I haven't heard of any substantial issues in those areas.

Then it shouldn't be difficult for you to prove that. After all, if it's that easy to implement, you ought to be able to fork Clang or GCC and implement it, right? If it's that easy to specify, then you should be able to write the appropriate standards wording for it.

It's easy to say that someone else's job is easy. It's not so easy when you have to do it yourself.

Well, I've attached a first patch against clang HEAD with this grammar change:

statement:
 
// ...
  compound
-statement
  compound
-statement '...'   // <-- new
 
// ....

It handles e.g.:

template <int... Is>
void bar()
{
 
{
    printf
("%d\n",Is);
 
}...

 
switch (1) {
   
{
     
case Is: printf("%d\n",Is);
   
}...
 
}
}
// ... bar<0,1,2>();

It will not handle nested structs and lamdas yet, e.g.:

template <int I> struct int_c {};

template <int I> struct MyType {
 
typedef int_c<I> type;
};

template <int... Is> void foo()
{
 
{
   
struct X {
      static void bar(typename MyType<Is>::type mt) {
        // error: pack expansion does not contain any unexpanded parameter packs
     
}
   
};
   
// ...
 
}...
}

and we probably want to disallow:

template <int... Is> void foo()
{
  {     // alternative: "([]{"
    struct X {
     
static void bar() {
        printf
("%d\n",Is);
     
}
   
}
 
}...  // alternative: "return 0; }() || ...);"  // does not work either
}

because then unexpanded parameter packs would have to "escape" a function body.
The first X::bar version does work, when the lambda+fold alternative is used instead, so that's obviously more achievable.

  Tobias 
clang_block_expansion-v0-r274740.patch

Michał Dominiak

unread,
Jul 7, 2016, 9:05:30 AM7/7/16
to std-pr...@isocpp.org
On Thu, Jul 7, 2016 at 2:53 PM smilingthax via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:
On Wednesday, July 6, 2016 at 3:25:22 AM UTC+2, Nicol Bolas wrote:
Things don't get standardized because someone posts a suggestion in a mailing list. Things get standardized because someone puts work and effort into it, pushing it forward at actual standards meetings.

Sure. But not all of us are in a position to participate in standards meetings.
 
You don't need to participate in a meeting yourself; all you'd need to do is 1) write a paper (and yes, this is considered effort, and if you really want something, you should put that effort into it), and 2) find somebody to champion the proposal. You are saying there's a lot of people wanting this (frankly I agree, myself included), which should mean that finding a champion for this shouldn't be very hard.

But the very first step in finding a champion is actually writing a paper, so that people may look at what is the actual thing you are proposing, what's the rationale, what problems it solves, ..., and possibly get interested in it.

smili...@googlemail.com

unread,
Jul 7, 2016, 9:08:34 AM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
On Thursday, July 7, 2016 at 8:33:50 AM UTC+2, Sergey Vidyuk wrote:

At least, I'd be helpful to have SOME guidance from the standardization body, whether they prefer such a simple solution or whether they'd like more work done on "for constexpr"(e.g.). 
And, if block/statement expansion won't move forward (for now), maybe add a more specific expansion for switch/case (something akin to the "first syntax idea" from my first mail)?

some info on for constexpr discussed here: https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-proposals/2hdpLnXJKkQ Right now I'm playing with gcc code (using my free time so progress is not rapid enough). I want to have implementation I can play with and share with other people on this list and then start to work on proposal paper.


What I don't like with for constexpr is that it does not work so well with switch/case, because it changes the destinations of continue/break. Also IMHO the syntax is quite verbose.
Some kind of inplace - template/type destructuring would be nice, but you quickly get to the point of wanting the full matching power of partial template specialization. So that would certainly be too much complexity.

Also note that some of the for constexpr can already be achieved by fold-expressions. Except for goto/return, that is:

template <typename T> struct type_c { typedef T type; };

template <typename... Ts> // ...
 
([&](auto t){
     
using T=typename decltype(t)::type;
     
//... instead of "continue;" -> "return true;"
     
//... instead of "break;" -> "return false;"
     
return true; // must be inserted at the end
   
}(type_c<Ts>()) && ... );

A similar transform is possible for non-type parameter packs.

  Tobias

inkwizyt...@gmail.com

unread,
Jul 7, 2016, 10:29:05 AM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
Yes, `for constexpr` is far more verbose but this give us lot more control over that happens. Dots work great when you want expand one parameter pack but if is more of them or you want start do some complex thing this will start be problematic.
Image you want use Cartesian product of two parameters pack. Without introducing completely new syntax you can't handle it. Of corse `for` have limitation too, but is lot of easier remove it.
Only thing that `for constexpr` need to handle nearly all possible use cases is allow it to extract template parameters from concrete templates (it would need have parameters like `template<typename... T>` or `template<auto... A>`):
// we have two packs, T1 and T2
template<typename...> pack{};

for constexpr (typename A : pack<T1..., T2...>) { } //pack merge
for constexpr (typename A : typename zip<pack<T1...>, pack<T2...>>::type) { } //zip two parameters pack, `type` is `pack<std::pair<T1, T2>...>`
for constexpr (typename A : pack<T1...>) { for constexpr (typename B : pack<T2...>) { } } //cross of two packs, this could be same pack too
for constexpr (typename A : pack<int, long, char>) { } //usage without any parameter pack and outside any template

And for `break` and `continue`. Only real problem there is `continue` because `break` can be easy fix by adding one after `for`. Another is this interaction should be well know for every one because is already same for normal loops:
while(y)
{
 
switch(x)
 
{
   
default: while(x)
   
{
     
--x;
     
case 1: --x;
     
case 5: --x; if (x == 7) break; //this is inner `while` break.
   
}
   
break; //break for quitting after inner `while`

   
case 10: x += 10; continue; //this is outer `while` continue.

   
for constexpr (int A : Cases...)
   
{
     
case 10 + A: func<A>(x);
     
if (A & 1) continue; //`for constexpr` continue
     
break; //`for constexpr` break
   
}
   
break; //break for quitting after `for`
 
}
}

I think losing `continue` for outer loop is worth price paying for this functionality.



smili...@googlemail.com

unread,
Jul 7, 2016, 12:34:47 PM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
On Thursday, July 7, 2016 at 4:29:05 PM UTC+2, inkwizyt...@gmail.com wrote:
On Thursday, July 7, 2016 at 3:08:34 PM UTC+2, smili...@googlemail.com wrote:
Yes, `for constexpr` is far more verbose but this give us lot more control over that happens. Dots work great when you want expand one parameter pack but if is more of them or you want start do some complex thing this will start be problematic.
Image you want use Cartesian product of two parameters pack. Without introducing completely new syntax you can't handle it.

You can implement product<pack<T1...>,pack<T2...>>::type as a library solution just like zip<..>. The only difference is that, for Us=pair<T1,T2>... you might want to add "using A = Us::first...;" and "using B = Us::second...;" for your convenience.
 
Of corse `for` have limitation too, but is lot of easier remove it.
Only thing that `for constexpr` need to handle nearly all possible use cases is allow it to extract template parameters from concrete templates (it would need have parameters like `template<typename... T>` or `template<auto... A>`):
// we have two packs, T1 and T2
template<typename...> pack{};

for constexpr (typename A : pack<T1..., T2...>) { } //pack merge
for constexpr (typename A : typename zip<pack<T1...>, pack<T2...>>::type) { } //zip two parameters pack, `type` is `pack<std::pair<T1, T2>...>`
for constexpr (typename A : pack<T1...>) { for constexpr (typename B : pack<T2...>) { } } //cross of two packs, this could be same pack too
for constexpr (typename A : pack<int, long, char>) { } //usage without any parameter pack and outside any template

And for `break` and `continue`. Only real problem there is `continue` because `break` can be easy fix by adding one after `for`. Another is this interaction should be well know for every one because is already same for normal loops: [...]
 
The for constexpr proposal meshes two things together that don't have to: One half is the semantic of "for", esp. wrt. to the creation of a break/continue-scope. The other half is the "inline"-introduction of a new parameter pack (i.e. without another indirection like function call / template class). 

The first half is IMO only weakly motivated; when you think of it, even the name "for constexpr" is not exactly fitting, because you're (most importantly) dealing with types, not constexpr values.

The second half can be dealt with without touching break/continue - or even forcing an expansion. Think about:

  using... Us = pack<int,char,long>;
 
// ...
 
{ doSomething<Us> }...
 
// ...

That's just a simple example, but the same thing works the same with product, zip, and all the stuff from above (you can add "using A = Ts::first;" as you like).

The real questions are: How will "using..." (or "for constexpr") know how to "iterate the pack", i.e. find the variadic part? 
When we look at how range-for was specified, we can see that it does not "bless" a certain "range"-type, but uses whatever iterator begin() / end() would return. So we're now talking about "meta"-iterators over types, like Boost MPL - or Fusion - or Hana - or ... has. It's not like the one true solution in this space has already been around long enough just waiting to get standardized.

Anyway, I don't think these considerations should hold us back from adding a simple statment/block-expansion facility to the language that already solves 95% of the most common use-cases.

  Tobias

 

Nicol Bolas

unread,
Jul 7, 2016, 12:46:36 PM7/7/16
to ISO C++ Standard - Future Proposals

I'm curious: how many proposals have actually gotten through the committee with just a champion?

inkwizyt...@gmail.com

unread,
Jul 7, 2016, 2:58:59 PM7/7/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com


On Thursday, July 7, 2016 at 6:34:47 PM UTC+2, smili...@googlemail.com wrote:
On Thursday, July 7, 2016 at 4:29:05 PM UTC+2, inkwizyt...@gmail.com wrote:
On Thursday, July 7, 2016 at 3:08:34 PM UTC+2, smili...@googlemail.com wrote:
Yes, `for constexpr` is far more verbose but this give us lot more control over that happens. Dots work great when you want expand one parameter pack but if is more of them or you want start do some complex thing this will start be problematic.
Image you want use Cartesian product of two parameters pack. Without introducing completely new syntax you can't handle it.

You can implement product<pack<T1...>,pack<T2...>>::type as a library solution just like zip<..>. The only difference is that, for Us=pair<T1,T2>... you might want to add "using A = Us::first...;" and "using B = Us::second...;" for your convenience.
 
Of corse `for` have limitation too, but is lot of easier remove it.
Only thing that `for constexpr` need to handle nearly all possible use cases is allow it to extract template parameters from concrete templates (it would need have parameters like `template<typename... T>` or `template<auto... A>`):
// we have two packs, T1 and T2
template<typename...> pack{};

for constexpr (typename A : pack<T1..., T2...>) { } //pack merge
for constexpr (typename A : typename zip<pack<T1...>, pack<T2...>>::type) { } //zip two parameters pack, `type` is `pack<std::pair<T1, T2>...>`
for constexpr (typename A : pack<T1...>) { for constexpr (typename B : pack<T2...>) { } } //cross of two packs, this could be same pack too
for constexpr (typename A : pack<int, long, char>) { } //usage without any parameter pack and outside any template

And for `break` and `continue`. Only real problem there is `continue` because `break` can be easy fix by adding one after `for`. Another is this interaction should be well know for every one because is already same for normal loops: [...]
 
The for constexpr proposal meshes two things together that don't have to: One half is the semantic of "for", esp. wrt. to the creation of a break/continue-scope. The other half is the "inline"-introduction of a new parameter pack (i.e. without another indirection like function call / template class). 


Yes, I try sneak two proposal at once, but this approach have one bug advantage. Using only one sentence in standard I can effective made "inline"-introduction of parameters packs. Trying doing this explicitly will probably need lot of more effort to write and getting it pass through committee.
 
The first half is IMO only weakly motivated; when you think of it, even the name "for constexpr" is not exactly fitting, because you're (most importantly) dealing with types, not constexpr values.

Its use syntax similar to iterating over container, in this case container is pack and object in that containers are types. I could say this is more abstract version of `for`.
 
The second half can be dealt with without touching break/continue - or even forcing an expansion. Think about:

  using... Us = pack<int,char,long>;
 
// ...
 
{ doSomething<Us> }...
 
// ...

That's just a simple example, but the same thing works the same with product, zip, and all the stuff from above (you can add "using A = Ts::first;" as you like).


There was already some discussion about stand alone parameter packs. You will need limit where they can be defined otherwise problems will arise:
{ someType<Us>::thisIsPackToo; }...
You need describe how handle this situation.

 
The real questions are: How will "using..." (or "for constexpr") know how to "iterate the pack", i.e. find the variadic part? 
When we look at how range-for was specified, we can see that it does not "bless" a certain "range"-type, but uses whatever iterator begin() / end() would return. So we're now talking about "meta"-iterators over types, like Boost MPL - or Fusion - or Hana - or ... has. It's not like the one true solution in this space has already been around long enough just waiting to get standardized.


At least for that I propose its simple. Extraction of pack can only be done for concrete template that have only variadic pack. And then we loop for each element of this pack in order as it appear in pack.
This will ignore lot of possible template parameters (like mixing types with values) but in most cases you can manually extract pack using template meta programing:

template<typename...>
struct pack
{
};

template<typename T>
struct Extract
{
};
template<template<typename... > typename A, typename... T>
struct Extract<A<T...>>
{
 
using type = pack<T...>;
};

template<typename A, typename B>
struct Z
{
};

using Y = typename Extract<Z<int, double>>::type; //pack<int, double>

Even if what you want isn't pack like class members mapped by hana you probably always can transform it using hana functionality to some pack that will work with `for`.

 

Sergey Vidyuk

unread,
Jul 8, 2016, 3:40:29 AM7/8/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
The real questions are: How will "using..." (or "for constexpr") know how to "iterate the pack", i.e. find the variadic part? 
When we look at how range-for was specified, we can see that it does not "bless" a certain "range"-type, but uses whatever iterator begin() / end() would return. So we're now talking about "meta"-iterators over types, like Boost MPL - or Fusion - or Hana - or ... has. It's not like the one true solution in this space has already been around long enough just waiting to get standardized.

The reason why runtime range for is specified in terms of begin/end function and iterators is nececsity to work with user provided collection types. In case of compile time for constexpr we have only one builtin collection type: variadic pack and it's easy to specify how it should be iterated. And I'm strongly against of introducing user types whith compile time collection semantic. It adds too much complexity to the C++ metaprogramming system which is overcomplicated already. We don't need to lazy iteration over bytes received from the socket transformet into parsed messages accessible as iterable range at compile time.

I feel that the discussion here can be much more usefull if we start to compare both appreaches "for constexpr" and "block expansion" for applicability in desired usecases.
  • Both aproaches solve the task to instantiate block of code for each member of variadic pack with straight forward and easy to read syntax. My personal opinion here is: we already have range for syntax for runtime iteration over collection and all of the developers are familiar with it. Block expansion syntax is good but it new syntax and from my point of vew reusing of existing syntax is better since all of the C++ developers who are not familiar with template metaprogramming (actually the biggest part of C++ users) will understand the code without any problems. But it's not too hard for average programmer to guess block expansion syntax anyway.
  • break and continue semantic of for-loop has poor motivation but it can be implemented in block expansion as well (for constexpr version is easier to read but this can only be used as argumemt if we will find strong motivation for the feature):
    template<typename... T>
    void foo_block_expanded(T... t) {
       
    {
           
    if constexpr (index_of_v<T, T...> < index_of_v<std::nullptr_t, T...>) { // break on std::nullptr_t in the type list
                bar
    (t);
               
    if constexpr (should_not_skipp_v<T>) // contunue if type should be skipped
                    do_the_thing
    (t);
            }
       
    }...
    }
  • Iteration over type list outside of template code is not directly proposed in both approaches but widely discussed. I don't like the idea of grabbing template parameters since almost any template in stl, boost and many other libraries has additional parameters with default or deduceable values. The result of the following loop "for constexpr (typename T, std::map<int, std::string>)" might be surprasing. I like the way D handles this task and how easy it can be adopted in C++ but it have to be additional proposal of quite independent feature which is actually usable for bigger set of tasks. Both "for constexpr" and "block expansion" may be extended to have iteration over types in non-template code:
    template<typename... T>
    struct types {
     using list = T;
    };


    void foo1() {
        for constexpr (typename T: types<int, std::string>::list) {
            T val;
            std::cin >> val;
            std::cout << val << std::endl;
        }
    }

    void foo2() {
        using typelist = types<int, std::string>::list;
        {
            typelist val;
            std::cint >> val;
            std::cout << val << std::endl;
        }...
    }
  • Task to unpack more then one parameters pack. Trivial with for constexpr but how the following code should be compiled:
template<typename... L1>
struct A {
  template<typename... L2>
  void foo(L2... a) {
    {
      {
        bar<L1>(a);
      }...
    }...

    {
      baz<L2>(a);
    }...
  }
};
I assume that baz<L2>(a) expansion should be turned into baz<L2[0]>(a[0]); ... baz<L2[N]>(a[N]); as it work in C++11 pack expansion rules. Looks like the inner part of the bar-expansion should work in a diferent way. I haven't found good way to solve this issue yet.

Sergey

smili...@googlemail.com

unread,
Jul 8, 2016, 6:21:36 AM7/8/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com, inkwizyt...@gmail.com
On Friday, July 8, 2016 at 9:40:29 AM UTC+2, Sergey Vidyuk wrote:
The real questions are: How will "using..." (or "for constexpr") know how to "iterate the pack", i.e. find the variadic part? 
When we look at how range-for was specified, we can see that it does not "bless" a certain "range"-type, but uses whatever iterator begin() / end() would return. So we're now talking about "meta"-iterators over types, like Boost MPL - or Fusion - or Hana - or ... has. It's not like the one true solution in this space has already been around long enough just waiting to get standardized.

The reason why runtime range for is specified in terms of begin/end function and iterators is nececsity to work with user provided collection types. In case of compile time for constexpr we have only one builtin collection type: variadic pack and it's easy to specify how it should be iterated. And I'm strongly against of introducing user types whith compile time collection semantic. It adds too much complexity to the C++ metaprogramming system which is overcomplicated already. We don't need to lazy iteration over bytes received from the socket transformet into parsed messages accessible as iterable range at compile time.

Well, lazy iteration over the cross-product of two packs would be nice... Boost MPL does most things lazy IIRC.
I believe we will see more/new standardized facilities to work with parameter packs, so that it is to early to commit to certain design-decisions with for constexpr. E.g. assume we have a ...[] operator, I could imagine something like this:
  for constexpr (int I=0; I<sizeof...(Ts); ++I) {
     foo
<Ts...[I]>();
 
}

 
I feel that the discussion here can be much more usefull if we start to compare both appreaches "for constexpr" and "block expansion" for applicability in desired usecases. [...]

Thank you. For now I want to zoom in on your last point: 
  • Task to unpack more then one parameters pack. Trivial with for constexpr but how the following code should be compiled:
template<typename... L1>
struct A {
  template<typename... L2>
  void foo(L2... a) {
    {
      {
        bar<L1>(a);
      }...
    }...

    {
      baz<L2>(a);
    }...
  }
};
I assume that baz<L2>(a) expansion should be turned into baz<L2[0]>(a[0]); ... baz<L2[N]>(a[N]); as it work in C++11 pack expansion rules. Looks like the inner part of the bar-expansion should work in a diferent way. I haven't found good way to solve this issue yet.

So we somehow have to specify which of the packs should be expanded:

// either:
   
{
     
using X=L1; // (error: declaration contains unexpanded parameter pack)
     
{
        bar
<X>(a);
     
}...
   
}...
// or:
   
{
     
auto x=a;   // (error: initializer contains unexpanded parameter pack 'a')
     
{
        bar
<L1>(x);
     
}...
   
}...

The errors in parenthesis show the errors currently produced with the clang-patch I posted earlier. The root cause is that I don't really know to to handle unexpanded parameter packs in declarations: The clang AST basically has three "root" types that are relevant: Stmt, Expr, Decl. Expressions could handle unexpanded parameter packs since C++11. I had to extend Statements to also handle them; but then there are Declarations... These can "occur" in Expressions as DeclRefExpr, or as statement: DeclStmt. I'm not sure how to handle the latter (big FIXME), because I think that we will allow unexpanded parameters packs no just inside of VarDecls. OTOH I'm not convinced that adding full support for unexpanded parameter packs to Declarations, as I did for Statements, would be wise. 

On a more abstract level, this means we have to look at and specify the interaction of unexpanded parameter packs in Declarations. As I mentioned earlier, it might be helpful to somewhat limit the "impact-range" of unexpanded parameter packs e.g. in nested class function bodies. I'm not too familiar with that part of the standard, though. 
Your example was already helpful in showing me that "baz<L1>" (instead of L2) already does work, i.e. the unexpanded parameter pack may come in from outside the function body.
I'd really appreciate some help on that front.

  Tobias

Reply all
Reply to author
Forward
0 new messages