statement folding on variadic templates parameters pack

1,908 views
Skip to first unread message

Sergey Vidyuk

unread,
Jun 8, 2016, 10:14:50 AM6/8/16
to ISO C++ Standard - Future Proposals
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

template<typename... L> void foo() {
   
for (typename T: L...) {
       
// do the stuff
   
}
}

template<typename... A> void foo(A&& a) {
    for (const auto& arg: a...) {
       
// do the stuff
   
}
}

In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.

It should also work for non-type parameter packs:

template<size_t... I> void foo() {
   
for (auto i: I...) {/*the code goes here*/}
    // as well as
    for (size_t i: I...) {/*the code goes here*/}
}

which can be implemented right now as

template<size_t... I> void foo() {
    for (size_t i: {I...}) {
/*the code goes here*/}
}

so this use case might not be really interresting.

I'm shure there were same idea duscussed somewhere in this list but I was unable to find them. I've seen proposals to add index access to parameters packs but there is no mechanism in the language to implement loop over constexpr indexes so index access do not solve tasks of statement folding. This proposal do not conflict with index access to a parameter packs.

As far as I know this syntax extension do not conflict with any other language feature and seems to be easy to implement in compilers. It's simple and convenient for average C++ programmers in contrast to recursive templates and should have better compilation speed.

Motivating example:

template<typename... V>
class variant_ptr {
public:
    template<typename Func, typename... A>
    auto invoke(Func&& f, A&&... a) const {
        size_t idx = 0;
        for (typename T: V...) {
            if (type_idx_ == idx++)
                return std::invoke(std::forward<Func>(f), reinterpret_cast<T*>(data_), std::forward<A>(a)...);
        }
        assert(false);
        throw std::logick_error("should never happen");
    }  

private:
    void* data_;
    size_t type_idx_;
};

Just try to ask experienced C++ programmer who is not template metaprogramming fan to write this in C++14 and check amount of boilerplate code and its redability if he will finish this task.

Are there any objections agains such syntax for C++? Is it worth to try to write proposal to add it?

Sergey Vidyuk

Greg Marr

unread,
Jun 8, 2016, 11:11:21 AM6/8/16
to ISO C++ Standard - Future Proposals
On Wednesday, June 8, 2016 at 10:14:50 AM UTC-4, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

Have you seen Sean Parent's for_each_argument?

template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
 
[](...){}((f(std::forward<Args>(args)), 0)...);
}



There was even a CppCon 2015 talk about it


Sergey Vidyuk

unread,
Jun 8, 2016, 2:03:33 PM6/8/16
to ISO C++ Standard - Future Proposals
Foreach from the presentation you've posted allows to repeat overloaded function or functor with multiple operator() for each argument from a pack by abusing coma operator. In C++17 it's even simplier since you don't need intermediate garbage initializer_list. You can perform expression folding directly and it terribly great (tried it with gcc 6.1).

This kind of folding is the simpliest one. I use it a lot but always feel myself sad using such kind of haks. In the example I've shown "if (cond) return foo(arg_from_pack);" statement is folded which can't be done by abusing coma opertor and requires recursive templates. One more thing on the syntax i propose: It allows to solve those kind of tasks without hacks or boilerplate code. It uses simple to write and understand construction (simple for human and for compiler).

Sergey Vidyuk

среда, 8 июня 2016 г., 21:11:21 UTC+6 пользователь Greg Marr написал:

Edward Catmur

unread,
Jun 9, 2016, 7:53:51 AM6/9/16
to ISO C++ Standard - Future Proposals
On Wednesday, 8 June 2016 19:03:33 UTC+1, Sergey Vidyuk wrote:
среда, 8 июня 2016 г., 21:11:21 UTC+6 пользователь Greg Marr написал:
On Wednesday, June 8, 2016 at 10:14:50 AM UTC-4, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

Have you seen Sean Parent's for_each_argument?

template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
 
[](...){}((f(std::forward<Args>(args)), 0)...);
}


Well, sure it's possible to emulate variadic for using existing language/library features, but a new language feature (built-in control flow construct) is more readable and integrates better; most of the same arguments for if constexpr (p0128r1) hold here as well.

I'm not sure about the motivating example, though; it seems to me that in that case what you really want is a switch statement:

template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {

   
switch constexpr (type_idx_) {
       
case... indexof(V): return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...);
   
}
   
throw std::logic_error("should never happen");
}

Do you have any other examples with stronger motivation e.g. where each iteration has a side effect, or where you conditionally break/return depending on something more complex than a switch-style predicate?

Aside from that, the syntax doesn't look to me to be distinctive enough; just having the for-range-initializer end in an ellipsis could be confused with e.g. a fold. (Yes, folds are parenthesized, but it's easy to overlook that.) Bikeshedding a bit, but I'd follow the "if constexpr" path and spell it "for constexpr". Perhaps we could end up with a full family of constexpr control flow statements.

Arthur O'Dwyer

unread,
Jun 9, 2016, 3:06:10 PM6/9/16
to ISO C++ Standard - Future Proposals
On Wednesday, June 8, 2016 at 7:14:50 AM UTC-7, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.

FWIW, I don't think that today's option, "abuse of comma operator folding", is very bad. It would be nice to have a fold-expression-esque (fold-statement?) syntax for folding over semicolons; but I have no concrete idea of what that would look like.
 
After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

template<typename... L> void foo() {
   
for (typename T: L...) {
       
// do the stuff
   
}
}

template<typename... A> void foo(A&& a) {
    for (const auto& arg: a...) {
       
// do the stuff
   
}
}

In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.

My kneejerk reaction was that for (typename T /*...*/) is a non-starter, because you're proposing to have it mean something very different from for (int I /*...*/).
Plus, you'll have troublesome grammar issues if the user tries to use the new syntax to replace a repeated statement where the repeated statement contains a "break" or "continue".
However, on further reflection, I think that I might just be approaching the problem from the wrong angle — I've been thinking of it as "a way to textually replace sizeof...(P) repeats of the same statement with a single statement" (i.e. a variant of folding), whereas I think you're thinking of it as "a way to control-flow over compile-time entities" (i.e. a variant of if-constexpr).  I actually think your syntax is reasonable with a very small tweak, and here's why:

There is currently a very well-received-in-Jacksonville proposal (P0292R1) to add "if constexpr" to the language.
If integer indexing into packs were proposed and accepted, then the obvious syntax for looping over a pack at compile-time would be

    for constexpr (size_t i=0; i < sizeof...(Ts); ++i) {  // new syntax ("for constexpr")
        using T = Ts...[i];  // new syntax (pack indexing)
        f<T>();
    }

or (arguably; anyway I'm not convinced that it's a horrible idea) that could be reduced to just

    for constexpr (typename T : Ts...) {  // new syntax ("for constexpr")
        f<T>();
    }

The remaining problem is that I'm not sure the "obvious" syntax (above) actually makes any sense. We want to be able to ++i, but at the same time use f<i>(). Perhaps i should magically be constexpr in the body of the loop, but non-constexpr in the header of the loop?
I've emailed Jens Maurer separately to ask what the semantics of if constexpr are supposed to be in similar cases, and will report back on the results if he doesn't post here first. :)

–Arthur

Richard Smith

unread,
Jun 9, 2016, 4:13:47 PM6/9/16
to std-pr...@isocpp.org
On Thu, Jun 9, 2016 at 12:06 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Wednesday, June 8, 2016 at 7:14:50 AM UTC-7, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.

FWIW, I don't think that today's option, "abuse of comma operator folding", is very bad. It would be nice to have a fold-expression-esque (fold-statement?) syntax for folding over semicolons; but I have no concrete idea of what that would look like.

I think last time this came up the reasonable options were:

expr_stmt() ... ;   // #1: problem: what does "if (blah) expr_stmt() ...;" mean?
expr_stmt(), ... ;  // #2: make outermost parens in comma fold optional in this context
( expr_stmt(), ... ) ; // #3: status quo, a little verbose

Supporting #2 seems very reasonable to me. In fact, we could make the parens in a fold-expression optional for any full-expression:

  std::cout << ... << pack;
  pack_of_references = ... = 0;

  if (pack_of_bools && ...)

There are other cases where we could allow it, but we need to be a bit careful:

  f(a + ... + b) // maybe ok?
  f(a, ... , b) // ill-formed

The rules we originally picked for fold-expressions were deliberately very conservative; relaxing them is probably a good idea.

After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

template<typename... L> void foo() {
   
for (typename T: L...) {
       
// do the stuff
   
}
}

template<typename... A> void foo(A&& a) {
    for (const auto& arg: a...) {
       
// do the stuff
   
}
}

In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.

My kneejerk reaction was that for (typename T /*...*/) is a non-starter, because you're proposing to have it mean something very different from for (int I /*...*/).
Plus, you'll have troublesome grammar issues if the user tries to use the new syntax to replace a repeated statement where the repeated statement contains a "break" or "continue".
However, on further reflection, I think that I might just be approaching the problem from the wrong angle — I've been thinking of it as "a way to textually replace sizeof...(P) repeats of the same statement with a single statement" (i.e. a variant of folding), whereas I think you're thinking of it as "a way to control-flow over compile-time entities" (i.e. a variant of if-constexpr).  I actually think your syntax is reasonable with a very small tweak, and here's why:

There is currently a very well-received-in-Jacksonville proposal (P0292R1) to add "if constexpr" to the language.
If integer indexing into packs were proposed and accepted, then the obvious syntax for looping over a pack at compile-time would be

    for constexpr (size_t i=0; i < sizeof...(Ts); ++i) {  // new syntax ("for constexpr")
        using T = Ts...[i];  // new syntax (pack indexing)
        f<T>();
    }

or (arguably; anyway I'm not convinced that it's a horrible idea) that could be reduced to just

    for constexpr (typename T : Ts...) {  // new syntax ("for constexpr")
        f<T>();
    }

The remaining problem is that I'm not sure the "obvious" syntax (above) actually makes any sense. We want to be able to ++i, but at the same time use f<i>(). Perhaps i should magically be constexpr in the body of the loop, but non-constexpr in the header of the loop?
I've emailed Jens Maurer separately to ask what the semantics of if constexpr are supposed to be in similar cases, and will report back on the results if he doesn't post here first. :)

–Arthur

--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f11d3266-3407-4c00-a578-5d07beeb1071%40isocpp.org.

Arthur O'Dwyer

unread,
Jun 9, 2016, 4:52:16 PM6/9/16
to ISO C++ Standard - Future Proposals
On Thu, Jun 9, 2016 at 1:13 PM, Richard Smith <ric...@metafoo.co.uk> wrote:
> On Thu, Jun 9, 2016 at 12:06 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
>>
>> FWIW, I don't think that today's option, "abuse of comma operator
>> folding", is very bad. It would be nice to have a fold-expression-esque
>> (fold-statement?) syntax for folding over semicolons; but I have no concrete
>> idea of what that would look like.
>
> I think last time this came up the reasonable options were:
>
> expr_stmt() ... ;   // #1: problem: what does "if (blah) expr_stmt() ...;"
> mean?
> expr_stmt(), ... ;  // #2: make outermost parens in comma fold optional in
> this context
> ( expr_stmt(), ... ) ; // #3: status quo, a little verbose

I don't care about any of the above options, because they're all isomorphic to #3, which we have today (well, in C++17 we will).  When I say "fold-statement" and "folding over semicolons", I mean folding that expands to things that aren't expr-stmts.

    template<typename... Ts>
    void f() {
        ( if (is_same_v<int, Ts>) return; )...;
        puts("None of my parameters are int.");
    }

Admittedly that's a toy example (I could just use if (any_of(...)) instead), and I don't have any non-toy examples of my own. I think Sergey's "variant_ptr::invoke" example is a good example of the problem, in that I'm having a lot of trouble refactoring it into anything that works equally well in C++17.


> The rules we originally picked for fold-expressions were deliberately very
> conservative; relaxing them is probably a good idea.

Agreed FWIW, but I think not useful for Sergey's use-case.

–Arthur

Edward Catmur

unread,
Jun 10, 2016, 5:19:52 AM6/10/16
to ISO C++ Standard - Future Proposals
On Thursday, 9 June 2016 21:52:16 UTC+1, Arthur O'Dwyer wrote:
On Thu, Jun 9, 2016 at 1:13 PM, Richard Smith <ric...@metafoo.co.uk> wrote:
> On Thu, Jun 9, 2016 at 12:06 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
>>
>> FWIW, I don't think that today's option, "abuse of comma operator
>> folding", is very bad. It would be nice to have a fold-expression-esque
>> (fold-statement?) syntax for folding over semicolons; but I have no concrete
>> idea of what that would look like.
>
> I think last time this came up the reasonable options were:
>
> expr_stmt() ... ;   // #1: problem: what does "if (blah) expr_stmt() ...;"
> mean?
> expr_stmt(), ... ;  // #2: make outermost parens in comma fold optional in
> this context
> ( expr_stmt(), ... ) ; // #3: status quo, a little verbose

I don't care about any of the above options, because they're all isomorphic to #3, which we have today (well, in C++17 we will).  When I say "fold-statement" and "folding over semicolons", I mean folding that expands to things that aren't expr-stmts.

    template<typename... Ts>
    void f() {
        ( if (is_same_v<int, Ts>) return; )...;
        puts("None of my parameters are int.");
    }

Admittedly that's a toy example (I could just use if (any_of(...)) instead), and I don't have any non-toy examples of my own. I think Sergey's "variant_ptr::invoke" example is a good example of the problem, in that I'm having a lot of trouble refactoring it into anything that works equally well in C++17.

I'm not sure that it is; it more motivates a switch than a for statement. The C++17 equivalent would look like this:

    template<typename Func, typename... A>
   
auto invoke(Func&& f, A&&... a) const {

       
return switch_(std::make_index_sequence<sizeof...(V)>{},
            type_idx_
,
           
[&] { return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...); }...,
           
[] { throw std::logic_error("should never happen"); }
       
);
   
}  

Here switch_ emulates a switch statement with variadic expansion of case labels:

template<class T, T... I, class... F, class D>
auto switch_(std::integer_sequence<T, I...>, T i, F&&... f, D&& d) {
   
switch (i) {
       
case... I : return std::forward<F>(f)();
   
}
   
return std::forward<D>(d)();
}

switch_ is tricky but not impossible to implement today; the choices are either to use recursion (with penalty on usability and performance) or to use the preprocessor (via Boost.Preprocessor) with a configurable upper limit on number of case statements.
 
> The rules we originally picked for fold-expressions were deliberately very
> conservative; relaxing them is probably a good idea.

Agreed FWIW, but I think not useful for Sergey's use-case.

If we had variadic expansion of case labels that would solve that particular use case, and very elegantly.

Sergey Vidyuk

unread,
Jun 10, 2016, 6:27:49 AM6/10/16
to ISO C++ Standard - Future Proposals
четверг, 9 июня 2016 г., 17:53:51 UTC+6 пользователь Edward Catmur написал:
On Wednesday, 8 June 2016 19:03:33 UTC+1, Sergey Vidyuk wrote:
среда, 8 июня 2016 г., 21:11:21 UTC+6 пользователь Greg Marr написал:
On Wednesday, June 8, 2016 at 10:14:50 AM UTC-4, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

Have you seen Sean Parent's for_each_argument?

template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
 
[](...){}((f(std::forward<Args>(args)), 0)...);
}


Well, sure it's possible to emulate variadic for using existing language/library features, but a new language feature (built-in control flow construct) is more readable and integrates better; most of the same arguments for if constexpr (p0128r1) hold here as well.

I'm not sure about the motivating example, though; it seems to me that in that case what you really want is a switch statement:

template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
   
switch constexpr (type_idx_) {
       
case... indexof(V): return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...);
   
}
   
throw std::logic_error("should never happen");
}

Do you have any other examples with stronger motivation e.g. where each iteration has a side effect, or where you conditionally break/return depending on something more complex than a switch-style predicate?


Example which also came from the real life but requires a bit more knowledge about the context. I have static reflection simulation library which is based on two template classes member_info (parametrized by pointer to member) and property_info (parametrized by pointer to getter member function and setter member function). Both of them have "static constexpr const char* name" member with the name of member or property, typedefs property_type and class_type and static get and set functions. Reflection information is std::tuple of those classes which is used as type and never as variable (since all of the tuple members are empty classes is'r useless). Now the example:

template<typename T, typename... PropertyInfo>
void set(T& obj, const std::string& property, const Json::Value& val) {
   
bool property_found = false;
   
for constexpr (typename PI: PropertyInfo...) {
       
static_assert(std::is_same_v<PI::class_type, T>);
       
if (PI::name != property)
           
continue;
        property_found
= true;
        PI
::set(obj, parse<PI::property_type>(val));
       
break;
   
}
   
if (!property_found)
       
throw std::runtime_error("property not found");
}

In case of DOM based JsonCpp parser this code looks like toy-example. But in the real life I'm using SAX based RapidJson parser and I have to provide callback which accepts json key name and change parsing event handler state to consume events related to corresponding json value properly and with such kind of constexpr for I can write the handler without recursive template.

This example can also be rewritten with 'case-folding' without loosing readability so if such feature will be added to the language I'll be really happy. I was thinking  on some example which is not "do something on the only element of a pack which satisfy condition evaluatable at runtime" and didn't find anything yet but I don't think that it's real show stopper for statement-folding based on constexpr foreach loop.
 
Aside from that, the syntax doesn't look to me to be distinctive enough; just having the for-range-initializer end in an ellipsis could be confused with e.g. a fold. (Yes, folds are parenthesized, but it's easy to overlook that.) Bikeshedding a bit, but I'd follow the "if constexpr" path and spell it "for constexpr". Perhaps we could end up with a full family of constexpr control flow statements.

Agree
 
for constexpr (typename T: L...) statement;
for constexpr (auto arg: args...) statement;


syntx might play better with the "if constexpr" syntax.

Sergey Vidyuk

unread,
Jun 10, 2016, 7:05:06 AM6/10/16
to ISO C++ Standard - Future Proposals


пятница, 10 июня 2016 г., 1:06:10 UTC+6 пользователь Arthur O'Dwyer написал:
On Wednesday, June 8, 2016 at 7:14:50 AM UTC-7, Sergey Vidyuk wrote:
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.

FWIW, I don't think that today's option, "abuse of comma operator folding", is very bad. It would be nice to have a fold-expression-esque (fold-statement?) syntax for folding over semicolons; but I have no concrete idea of what that would look like.

Abuse of a coma operator for function call folding is bad since this approach requires protection from operator,() overload and it's some way to achieve what you want without direct instrument to do it. I've never used operator coma before variadic templates were added to the language. I think it's time to think on adding direct instrument to repeat statements for each item from a parameter pack. I think that semicolon folding whill be worse readable and convenient to most of the C++ developers then extended syntax of for-loop since most of us are writing imperative code with lots of loops and rarely use some folding technics (reduce on therad pool with Qt Concurent is the only way I've used folding in my C++ code apart from meta-programming with variadic templates).
 
 
After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:

template<typename... L> void foo() {
   
for (typename T: L...) {
       
// do the stuff
   
}
}

template<typename... A> void foo(A&& a) {
    for (const auto& arg: a...) {
       
// do the stuff
   
}
}

In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.

My kneejerk reaction was that for (typename T /*...*/) is a non-starter, because you're proposing to have it mean something very different from for (int I /*...*/).
Plus, you'll have troublesome grammar issues if the user tries to use the new syntax to replace a repeated statement where the repeated statement contains a "break" or "continue".

As it was told before by Edward Catmur for constexpr (typename T: L...) might be better since it might be hard to distinguish three dots from expression folding for human reader in some cases.

There are no problems with the languate grammar as far as I can see. Loop operators break and continue should also work in the same way as in normal loop. Jump to the beggining of the next iteration instantiation or to the end of the last one. It only affect semantic since those operators will work on a nearest loop which is constexpr for loop. With such kind of extension of for-loop syntax such semantic change will confuse noone since adding inner loop do the same and all of the C++ developers know it and can handle it.
 
However, on further reflection, I think that I might just be approaching the problem from the wrong angle — I've been thinking of it as "a way to textually replace sizeof...(P) repeats of the same statement with a single statement" (i.e. a variant of folding), whereas I think you're thinking of it as "a way to control-flow over compile-time entities" (i.e. a variant of if-constexpr).  I actually think your syntax is reasonable with a very small tweak, and here's why:

There is currently a very well-received-in-Jacksonville proposal (P0292R1) to add "if constexpr" to the language.
If integer indexing into packs were proposed and accepted, then the obvious syntax for looping over a pack at compile-time would be

    for constexpr (size_t i=0; i < sizeof...(Ts); ++i) {  // new syntax ("for constexpr")
        using T = Ts...[i];  // new syntax (pack indexing)
        f<T>();
    }

or (arguably; anyway I'm not convinced that it's a horrible idea) that could be reduced to just

    for constexpr (typename T : Ts...) {  // new syntax ("for constexpr")
        f<T>();
    }

The remaining problem is that I'm not sure the "obvious" syntax (above) actually makes any sense. We want to be able to ++i, but at the same time use f<i>(). Perhaps i should magically be constexpr in the body of the loop, but non-constexpr in the header of the loop?
I've emailed Jens Maurer separately to ask what the semantics of if constexpr are supposed to be in similar cases, and will report back on the results if he doesn't post here first. :)

–Arthur

As you mentioned for constexpr version of a classical for-loop is quite complicated challenge so I propose to extend range-for loop as a first step since there are no explicit mutation statements which should change some context which is constexpr from a loop body point of view. Rage based for combined with std::index_sequence and inde access on parameter packs can give you somethig really similair to classic indexed iteration:

template<typename TupleType, size_t... I>
void print_tuple(const TupleType& tpl, std::index_sequence<I...>) {
    for constexpr(constexpr size_t idx: I...) {
        std::cout << std::get<idx>(tpl) << std::endl;
    }
}

idx here can be treated as local variable in a scope of single iteration instantiation and no "constexpr mutation allowed only inside for-loop statemets only" required.

Sergey Vidyuk

unread,
Jun 10, 2016, 7:25:37 AM6/10/16
to ISO C++ Standard - Future Proposals

    for constexpr (size_t i=0; i < sizeof...(Ts); ++i) {  // new syntax ("for constexpr")
        using T = Ts...[i];  // new syntax (pack indexing)
        f<T>();
    }
 
I can see one more issue with such kind of for-loop extension: it's impossible to prove that the loop will terminate in generic case so some (probably implementation defined) limit on a number of body instantiations required. Range based for over variadic parameters pack have no such problem since number of items in pack known by compiler before loop instantiation.

inkwizyt...@gmail.com

unread,
Jun 10, 2016, 3:10:04 PM6/10/16
to ISO C++ Standard - Future Proposals

 I think that `for constexpr` should be allowed to extracting parameters from templates, some thing like that:
using type = std::tuple<int, long long, char>;

int main()
{
   
for constexpr (typename T : type) //without `...`
   
{
        f
<T>();
   
} //equal: `f<int>(); f<long long>(); f<char>();`
}

This will allow usage outside of template function and simplify complex operation:
template<typename T>
using MetaFunc = std::tuple<int, T, T, char>;


int main()
{
   
for constexpr (typename T : MetaFunc<long>)
   
{
        f
<T>();
   
} //equal: `f<int>(); f<long>(); f<long>(); f<char>();`
   
for constexpr (typename T : MetaFunc<char>)
   
{
       
for constexpr (typename TT : MetaFunc<T>)
       
{
           g
<T,TT>();
       
}
   
}
}


inkwizyt...@gmail.com

unread,
Jun 10, 2016, 3:22:57 PM6/10/16
to ISO C++ Standard - Future Proposals
 With `for constexpr` we should have `switch` for free:

template<typename... Types>
void select(int i)
{
   
switch (i)
   
{
       
for constexpr (typename T : Types...)
       
{
           
case T::id: T::func();
           
break; //break for `for constexpr`
       
}
       
break; //break for `switch`
   
default:
        f
();
       
break;
   
}
}



Matt Calabrese

unread,
Jun 10, 2016, 4:08:58 PM6/10/16
to ISO C++ Standard - Future Proposals
On Fri, Jun 10, 2016 at 2:19 AM, Edward Catmur <e...@catmur.co.uk> wrote:
I'm not sure that it is; it more motivates a switch than a for statement. The C++17 equivalent would look like this:

    template<typename Func, typename... A>
   
auto invoke(Func&& f, A&&... a) const {

       
return switch_(std::make_index_sequence<sizeof...(V)>{},
            type_idx_
,
           
[&] { return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...); }...,
           
[] { throw std::logic_error("should never happen"); }
       
);
   
}  

Here switch_ emulates a switch statement with variadic expansion of case labels:

template<class T, T... I, class... F, class D>
auto switch_(std::integer_sequence<T, I...>, T i, F&&... f, D&& d) {
   
switch (i) {
       
case... I : return std::forward<F>(f)();
   
}
   
return std::forward<D>(d)();
}

switch_ is tricky but not impossible to implement today; the choices are either to use recursion (with penalty on usability and performance) or to use the preprocessor (via Boost.Preprocessor) with a configurable upper limit on number of case statements.
 
> The rules we originally picked for fold-expressions were deliberately very
> conservative; relaxing them is probably a good idea.

Agreed FWIW, but I think not useful for Sergey's use-case.

If we had variadic expansion of case labels that would solve that particular use case, and very elegantly.

+1

There was a library that went through the boost review process called Boost.Switch by Steven Watanabe almost 10 years ago now (prior to C++11) that did this and it was accepted pending addressing of some comments, though it never ended up getting added to boost. I've contacted the author multiple times over the years because I've found it useful on many occasions and I use an updated version in my personal projects to this day (I mention it in P0376R0). The docs are still hosted by someone at http://dancinghacker.com/switch/ . It clearly had and has limits even now that we have variadic templates. Making switch or a switch-like facility that allows cases to be formed from from a variadic expansion would be really useful. It doesn't necessarily have to exactly be our current language-level switch nor imply extension of variadic expansion to statements. A library-level version that doesn't support fall-through and always breaks would be fine for all of the use-cases I've ever encountered. If it were a standard library facility, it could even be implemented via some kind of compiler intrinsics that users wouldn't need to be aware of and would allow it to work without preprocessor expansion.

ryani...@gmail.com

unread,
Jun 10, 2016, 8:55:39 PM6/10/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com
On Friday, June 10, 2016 at 12:22:57 PM UTC-7, inkwizyt...@gmail.com wrote:
 With `for constexpr` we should have `switch` for free:

template<typename... Types>
void select(int i)
{
   
switch (i)
   
{
       
for constexpr (typename T : Types...)
       
{
           
case T::id: T::func();
           
break; //break for `for constexpr`
       
}
       
break; //break for `switch`
   
default:
        f
();
       
break;
   
}
}


Bikeshed: I don't think "..." is required after "Types"; you don't write "for (x : vs...)"

Usages:

template <typename... Types>
int f()
{
    int count = 0;
    for constexpr (typename T : Types)
    {
        if(is_trivially_constructible<T>::value) count++;
    }
    return count;
}

// Reaching far into the future, when constexpr is even more magic,
// we might see code like this:

template <int... xs>
constexpr int sum()
{
    int total = 0;
    for constexpr(int x : xs)
        total += x;
    return total;
}

which seems much more readable than the current way you calculate this function (and much more like 'idiomatic' C/C++)

Edward Catmur

unread,
Jun 11, 2016, 4:45:38 AM6/11/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com, ryani...@gmail.com


On Saturday, 11 June 2016 01:55:39 UTC+1, ryani...@gmail.com wrote:
Bikeshed: I don't think "..." is required after "Types"; you don't write "for (x : vs...)"

Usages:

template <typename... Types>
int f()
{
    int count = 0;
    for constexpr (typename T : Types)
    {
        if(is_trivially_constructible<T>::value) count++;
    }
    return count;
}

That can be done already today (in --std=c++1z mode) with a fold:

template<class... Ts>
constexpr int f() {
   
return (0 + ... + (is_trivially_constructible<Ts>::value ? 1 : 0));
}

 
// Reaching far into the future, when constexpr is even more magic,
// we might see code like this:

template <int... xs>
constexpr int sum()
{
    int total = 0;
    for constexpr(int x : xs)
        total += x;
    return total;
}

which seems much more readable than the current way you calculate this function (and much more like 'idiomatic' C/C++)

And that, too:

template<int... Is>
constexpr int sum() {
   
return (0 + ... + Is);
}

inkwizyt...@gmail.com

unread,
Jun 11, 2016, 5:29:23 AM6/11/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com, ryani...@gmail.com
 I think that `...` would be useful, I have suggestion to allow iterate through parameters of template:
void f() //normal function, not need to be template or be in template class
{
   
for constexpr (typename T : std::tuple<int, long, int>)
   
{
        g
<T>();
   
} //equal: `g<int>(); g<long>(); g<int>();`
}
With `...` we could easy distinguish both cases and reduce possible changes in behavior wen switch from single template to variadic template.
And probably biggest gran of this would be reduce of boilerplate required to use `for constexpr`.

Ryan Ingram

unread,
Jun 11, 2016, 5:33:36 AM6/11/16
to std-pr...@isocpp.org, inkwizyt...@gmail.com
> This can be done already today with a fold

I used intentionally toy examples, everyone knows you demo for loops with something simple like "sum" before you move on to real examples.

I really like this proposal; consider this slight modification:

int total = 0;
for constexpr(int x : xs)
{
    if(x == 0) break;
    if(x & 1) total += x;
}
return total;

It's a lot harder to implement with a c++1z fold.  You can almost certainly do it with a custom type and operator overloading, or perhaps by abusing the comma and ternary operators, but it's a lot uglier and I bet obscures the meaning.

bool broken = false;
int total = 0;
((broken = broken || (xs == 0), total += ((xs & 1) != 0 && !broken) ? xs : 0), ... );
return total;

I love folds.  I write Haskell all the time.  In C and C++ the idiomatic way to express folds is with a looping construct and a local state variable.  Why try to fight that?  "for" has a ton of hidden power and is understood by everyone who writes C++ code.  Avoiding it is like saying the only way to loop in Haskell is via foldr -- sure, you can implement everything you want with just foldr, but lots of operations are clearer using different and/or more powerful tools.


--
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/2hdpLnXJKkQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Edward Catmur

unread,
Jun 11, 2016, 7:01:58 AM6/11/16
to std-pr...@isocpp.org


On 11 Jun 2016 10:33 a.m., "Ryan Ingram" <ryani...@gmail.com> wrote:
>
> > This can be done already today with a fold
>
> I used intentionally toy examples, everyone knows you demo for loops with something simple like "sum" before you move on to real examples.
>
> I really like this proposal; consider this slight modification:
>
> int total = 0;
> for constexpr(int x : xs)
> {
>     if(x == 0) break;
>     if(x & 1) total += x;
> }
> return total;
>
> It's a lot harder to implement with a c++1z fold.  You can almost certainly do it with a custom type and operator overloading, or perhaps by abusing the comma and ternary operators, but it's a lot uglier and I bet obscures the meaning.

Agreed, folds only work well for simple cases. But you don't need for constexpr to write your algorithm; you can use normal iteration over an initializer list:

int total = 0;
for (int x : {xs...})


{
    if(x == 0) break;
    if(x & 1) total += x;
}
return total;

(And the ranges guys might have their own opinions on how better to write this.)

I'm in favor of this proposal! But I need to see motivating examples that are actually difficult to write today; it would wreck the proposal to be presented along with examples that can easily be dismissed as already easy to code.

To my mind, that means that examples need to actually exploit the distinctive feature of for constexpr, which is that types (and templates and constants) can be different from one iteration of the loop to the next, while (preferably) also taking advantage of the fine-grained flow control we get from the loop control statements. Otherwise, you could just use boost::mpl::for_each, or (ugh) a comma fold.

Arthur O'Dwyer

unread,
Jun 11, 2016, 4:46:15 PM6/11/16
to ISO C++ Standard - Future Proposals
The non-range variant of "for constexpr" does seem more problematic than the range variant, but I don't think termination is a problem here. "Can't prove termination" is not a new problem (for computer science or for C++ in particular), and the existing solutions/workarounds apply. The compiler already has to do something reasonable with e.g.

constexpr int f() { int i = 42; while (true) ++i; return i; }
int main() {
    static int x[f()];
}

Clang on my machine does this:

x.cc:1:15: error: constexpr function never produces a constant expression [-Winvalid-constexpr]

constexpr int f() { int i = 42; while (true) ++i; return i; }

              ^

x.cc:1:46: note: constexpr evaluation hit maximum step limit; possible infinite loop?

constexpr int f() { int i = 42; while (true) ++i; return i; }

                                             ^

The (minor) advantage of the general-purpose for-loop syntax as opposed to the ranged-over-pack syntax is that for any given loop-of-N-iterations, the latter requires the compiler to construct an actual entity, a pack-of-N-elements; whereas the former doesn't. They both take O(N) time, but only the latter takes O(N) space as well.
In practice, determining N for an arbitrary input program is equivalent to the Halting Problem, so compilers must implement resource limiting logic similar to the Clang error message above. The practical limits on N for "time" can be pretty high; whereas the practical limits on N for "space" will in practice tend to be lower.

Clang seems to put a limit of 512 recursions within a constexpr function, but allows upwards of 1000 iterations of loop control flow within a single constexpr function. This matches with my intuition: Recursion requires space, and is therefore capped tightly. Iteration requires only time, and therefore is capped more loosely.  Therefore, any time the user-programmer can make the tradeoff (more time <-> less space) in a constexpr context, he should do so — and ideally the language should provide the tools for doing so (examples: C++14's unrestricting constexpr functions, C++17's fold-expressions).

–Arthur

inkwizyt...@gmail.com

unread,
Jun 12, 2016, 6:23:18 AM6/12/16
to ISO C++ Standard - Future Proposals


On Saturday, June 11, 2016 at 1:01:58 PM UTC+2, Edward Catmur wrote:

I'm in favor of this proposal! But I need to see motivating examples that are actually difficult to write today; it would wreck the proposal to be presented along with examples that can easily be dismissed as already easy to code.

To my mind, that means that examples need to actually exploit the distinctive feature of for constexpr, which is that types (and templates and constants) can be different from one iteration of the loop to the next, while (preferably) also taking advantage of the fine-grained flow control we get from the loop control statements. Otherwise, you could just use boost::mpl::for_each, or (ugh) a comma fold.


For me biggest grain would be interaction with `return`, `case` and `goto`:
template<typename... Op>
int CodeInterpretator(Uint8* code)
{
   
int ret = 0;
   
OpCodeState state;
    beginLoop
: while (true)
   
{
       
switch (*code++)
       
{
           
for constexpr (typename T : Op...)
           
{
           
case T::OpCodeId:
                ret
= T::OpCodeFunc(&state);
               
if (ret)
                   
goto specialCases;
               
else
                   
break;
           
}
           
break;
       
default:
           
Unreachable();
       
}
   
}
    specialCases
:
   
if (ret == R_RETURN)
       
return state.RetValue;
   
else if (ret == R_JUMP)
        code
= *(Uint8**)code;
   
else if (ret == R_ERROR)
       
throw OpException("Something go wrong", state);
   
goto beginLoop;
}
This code should be fast as manually written switch, it would be trivial for compiler to create jump table to have best performance.
Only solution that are current available require deep recursion that very easy break inlining. Not mentioning that you need chop logic in small chunks that will be hard to maintain.


Ville Voutilainen

unread,
Jun 12, 2016, 6:25:50 AM6/12/16
to ISO C++ Standard - Future Proposals
On 12 June 2016 at 13:23, <inkwizyt...@gmail.com> wrote:
> This code should be fast as manually written switch, it would be trivial for
> compiler to create jump table to have best performance.


I'm sure the open-source compilers will happily accept an extension
patch that shows how trivial it is.

inkwizyt...@gmail.com

unread,
Jun 12, 2016, 7:13:48 AM6/12/16
to ISO C++ Standard - Future Proposals
I was referring to switch after applying `for constexpr`. Some thing like that:
switch (x)

{
   
for constexpr (typename T : Op...)
   
{

   
case T::OpId: T::OpFunc(); break;
   
}
   
break;
}
//After `for constexpr` "unroll"
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); break;
   
case Op1::OpId: Op1::OpFunc(); break;
   
case Op2::OpId: Op2::OpFunc(); break;
   
case Op3::OpId: Op3::OpFunc(); break;
}
And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.

Tony V E

unread,
Jun 12, 2016, 11:39:41 AM6/12/16
to ISO C++ Standard - Future Proposals
How do I break from the for constexpr, if 'break' doesn't do it?

Sent from my BlackBerry portable Babbage Device
Sent: Sunday, June 12, 2016 7:13 AM
To: ISO C++ Standard - Future Proposals
Subject: Re: [std-proposals] Re: statement folding on variadic templates parameters pack

--
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.

inkwizyt...@gmail.com

unread,
Jun 12, 2016, 1:22:40 PM6/12/16
to ISO C++ Standard - Future Proposals


On Sunday, June 12, 2016 at 5:39:41 PM UTC+2, Tony V E wrote:
How do I break from the for constexpr, if 'break' doesn't do it?

Sent from my BlackBerry portable Babbage Device
Sent: Sunday, June 12, 2016 7:13 AM
To: ISO C++ Standard - Future Proposals
Subject: Re: [std-proposals] Re: statement folding on variadic templates parameters pack

I was referring to switch after applying `for constexpr`. Some thing like that:
switch (x)
{
   
for constexpr (typename T : Op...)
   
{
   
case T::OpId: T::OpFunc(); break;
   
}
   
break;
}
//After `for constexpr` "unroll"
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); break;
   
case Op1::OpId: Op1::OpFunc(); break;
   
case Op2::OpId: Op2::OpFunc(); break;
   
case Op3::OpId: Op3::OpFunc(); break;
}
And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.
 

`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); goto finalFor;
   
case Op1::OpId: Op1::OpFunc(); goto finalFor;
   
case Op2::OpId: Op2::OpFunc(); goto finalFor;
   
case Op3::OpId: Op3::OpFunc(); goto finalFor;
    finalFor
: break;
}


Arthur O'Dwyer

unread,
Jun 12, 2016, 3:32:22 PM6/12/16
to ISO C++ Standard - Future Proposals
On Sun, Jun 12, 2016 at 10:22 AM, <inkwizyt...@gmail.com> wrote:
On Sunday, June 12, 2016 at 5:39:41 PM UTC+2, Tony V E wrote:

I was referring to switch after applying `for constexpr`. Some thing like that:
switch (x)
{
   
for constexpr (typename T : Op...)
   
{
   
case T::OpId: T::OpFunc(); break;
   
}
   
break;
}
//After `for constexpr` "unroll"
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); break;
   
case Op1::OpId: Op1::OpFunc(); break;
   
case Op2::OpId: Op2::OpFunc(); break;
   
case Op3::OpId: Op3::OpFunc(); break;
}
And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.
 
 How do I break from the for constexpr, if 'break' doesn't do it?


`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); goto finalFor;
   
case Op1::OpId: Op1::OpFunc(); goto finalFor;
   
case Op2::OpId: Op2::OpFunc(); goto finalFor;
   
case Op3::OpId: Op3::OpFunc(); goto finalFor;
    finalFor
: break;
}

No, that doesn't work.  I think Tony has a point.
Sergey's half-proposal wants to make "break" and "continue" inside a "for constexpr" act exactly like "break" and "continue" inside a non-constexpr "for":

    for constexpr(typename T : Ts...) {
        do_something<T>();
        if constexpr(is_same_v<T, nullptr_t>) break;
    }

This code would generate an unrolled loop of do_something<T>()s for each T in Ts... up to and including the first instance of nullptr_t. As soon as it generated do_something<nullptr_t>(), it would break and stop iterating over Ts....

You can't have break work this way (Sergey's way) and also have it work for outer switch statements; you have to pick one. And I think you've picked the wrong one, for two reasons:

First, Ed Catmur has shown that if what you really want is a way to pack-expand switch cases, there might be more natural syntaxes to express that.

    // Natively (this syntax strikes me as ungraceful, I admit)
    switch (x) {
        case Op::OpId: { Op::OpFunc(); break; }...
    }

    // Or via some sort of library template
    auto switch_ = make_switch(
        make_switch_case( Op::OpId, [](){ return Op::OpFunc(); } )...
    );
    auto result = switch_.on(x) + switch_.on(y);

    template<class X, class... Cs>
    auto switch_on(X&& x, Cs&&... cases) {
        return make_switch(std::forward<Cs>(cases)...).on(std::forward<X>(x));
    }

Second, I think you're doing the same thing I did at first, which is to treat "for constexpr" (or whatever) as a pack-expansion primitive in the same vein as fold-expressions, whereas what it really wants to be is a compile-time control-flow (code-generation) primitive in the same vein as "if constexpr" and function templates. If you stop thinking of it as physically duplicating/unrolling its contents (the way a fold-expression does), then you no longer have to worry about what unrolling a "break" or "continue" statement would mean.

I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.

–Arthur

inkwizyt...@gmail.com

unread,
Jun 12, 2016, 7:52:03 PM6/12/16
to ISO C++ Standard - Future Proposals


On Sunday, June 12, 2016 at 9:32:22 PM UTC+2, Arthur O'Dwyer wrote:
On Sun, Jun 12, 2016 at 10:22 AM, <inkwizyt...@gmail.com> wrote:
On Sunday, June 12, 2016 at 5:39:41 PM UTC+2, Tony V E wrote:
 How do I break from the for constexpr, if 'break' doesn't do it?


`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); goto finalFor;
   
case Op1::OpId: Op1::OpFunc(); goto finalFor;
   
case Op2::OpId: Op2::OpFunc(); goto finalFor;
   
case Op3::OpId: Op3::OpFunc(); goto finalFor;
    finalFor
: break;
}

No, that doesn't work.  I think Tony has a point.
Sergey's half-proposal wants to make "break" and "continue" inside a "for constexpr" act exactly like "break" and "continue" inside a non-constexpr "for":

    for constexpr(typename T : Ts...) {
        do_something<T>();
        if constexpr(is_same_v<T, nullptr_t>) break;
    }

This code would generate an unrolled loop of do_something<T>()s for each T in Ts... up to and including the first instance of nullptr_t. As soon as it generated do_something<nullptr_t>(), it would break and stop iterating over Ts....

You can't have break work this way (Sergey's way) and also have it work for outer switch statements; you have to pick one. And I think you've picked the wrong one, for two reasons:

First, Ed Catmur has shown that if what you really want is a way to pack-expand switch cases, there might be more natural syntaxes to express that.

    // Natively (this syntax strikes me as ungraceful, I admit)
    switch (x) {
        case Op::OpId: { Op::OpFunc(); break; }...
    }


I start from that syntax and after that I switch to `for constexpr` because it was for me lot more readable and have more usages.
 
    // Or via some sort of library template
    auto switch_ = make_switch(
        make_switch_case( Op::OpId, [](){ return Op::OpFunc(); } )...
    );
    auto result = switch_.on(x) + switch_.on(y);

    template<class X, class... Cs>
    auto switch_on(X&& x, Cs&&... cases) {
        return make_switch(std::forward<Cs>(cases)...).on(std::forward<X>(x));
    }


But it would be hard to generate code from it that will be comparable with native `switch`.
 
Second, I think you're doing the same thing I did at first, which is to treat "for constexpr" (or whatever) as a pack-expansion primitive in the same vein as fold-expressions, whereas what it really wants to be is a compile-time control-flow (code-generation) primitive in the same vein as "if constexpr" and function templates. If you stop thinking of it as physically duplicating/unrolling its contents (the way a fold-expression does), then you no longer have to worry about what unrolling a "break" or "continue" statement would mean.

I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.

–Arthur

 One question how will work this code?
switch (0)
{
   
for (;;)
   
{
        f
();
       
case 0: g(); break;
   
}
    h
();
}
It will call `g` and then `h` because `break` is from `for` not from `switch`.
Now with `for constexpr`:
switch (0)
{
   
for constexpr (int I : II...)
   
{
        f
<I>()
       
case I: g<I>(); break;
   
}
    h
();
}
It will call `g<0>` and then `h`. Exactly same as for normal `for`. Only change is how `case` is handled.

Overall I assume that `for constexpr` will conceptually "unroll" body for each parameter pack element. This is need because each "iteration" body can be completely different:
for constexpr (typename T : TT...)
{
    T x
; //this will require creating separate variable for each iteration, but they lifetime not overlap.
}
Probably only real difference is when some iteration could create invalid code. In my approach it will always be invalid, in case of "dynamic" control-flow it can be valid if `for` leave early.
I think It could be possible to tweak both approach to have same output based on reachability of each iteration body. If is unreachable it will be not generated. Adding `case` will made all iteration reachable and generated.


Edward Catmur

unread,
Jun 12, 2016, 9:17:53 PM6/12/16
to ISO C++ Standard - Future Proposals
On Sunday, 12 June 2016 20:32:22 UTC+1, Arthur O'Dwyer wrote:
I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.

Too many times to count, I'm sure! I decided to take another go at it, trying to conform to the Boost.Switch interface but using modern techniques, using only the standard library and Boost.Preprocessor for iteration. My implementation is a little under 60 lines: https://gist.github.com/ecatmur/7f5f8b44e70f414742e4eae1efdd0ca7 

#define SWITCH_CASE(Z,N,_) \
 
case std::tuple_element_t<N, std::tuple<std::integral_constant<T, C>...>>{} : \
   
return std::forward<F>(f)( \
      std
::tuple_element_t<N, std::tuple<std::integral_constant<T, C>...>>{}); \
   
//
#define SWITCH_SPECIALIZATION(Z,N,_) \
template<class T, T... C, class F> \
struct Switch<N, std::integer_sequence<T, C...>, F> { \
 
using R = std::common_type_t<std::result_of_t<F(std::integral_constant<T, C>)>...>; \
  R
operator()(T t, F&& f) { \
   
switch(t) { BOOST_PP_REPEAT_ ## Z(N, SWITCH_CASE, nil) } \
 
} \
 
/* ... */ \
};
BOOST_PP_REPEAT
(SWITCH_MAX, SWITCH_SPECIALIZATION, nil)

Much of the effort is in handling the return type from the default case if provided; it'd be a lot easier if noreturn was part of the type system or we had a true bottom type.

Arthur O'Dwyer

unread,
Jun 12, 2016, 11:11:45 PM6/12/16
to ISO C++ Standard - Future Proposals
Interesting!  My implementation ended up at twice the length of yours, but IMHO has a nicer user interface than Boost.Switch.


The "apply" example in my case looks like this: extra boilerplate to deal with making index sequences, but the tradeoff is that my interface allows you to switch on things that aren't consecutive integers.
The other tradeoff is that my Clang isn't smart enough to turn this into a switch. It will go as far as inlining everything into an if-else chain (no function calls), but it won't make a jump table. Yes, this kind of defeats the purpose; but I'm holding out hope that it's a Quality of Implementation issue rather than a fundamental flaw.

template<class... V, class F>
auto apply(VariantPtr<V...> p, F&& f) {
    return detail_apply_(p, std::forward<F>(f), std::make_index_sequence<sizeof...(V)>{});
}

template<class... V, class F, size_t... Is>
auto detail_apply_(VariantPtr<V...> p, F&& f, std::index_sequence<Is...>) {
    return xstd::switch_on(p.idx,
        xstd::make_switch_case(Is, [&](){
            using elt_type = std::tuple_element_t<Is, std::tuple<V...>>;
            return std::forward<F>(f)(static_cast<elt_type*>(p.data));
        })...
    );
}


Your "enum to integer" example looks like this, which I admit isn't an improvement on the status quo syntactically, but I'm fine with that. The "enum to integer" example in your gist doesn't compile on my Clang because it is trying to instantiate std::integer_sequence<E, ...> where E (being an enum) is not an integral type; I think you have to add some boilerplate involving std::conditional_t<std::is_enum_v<E>, std::underlying_type_t<E>, E> and some explicit casts, at which point it doesn't even seem worth it anymore. Having to create an entity of the form foo<..., E::A, E::B, E::C> should have been a red flag anyway, IMHO.


enum class E { A, B, C = 5 };

template<class E, E... Es>
int integer_value_of(E e) {
    return xstd::switch_on(e,
        xstd::make_switch_case(Es, []() { return int(Es); })...
    );
}

int f(E e) {
    return integer_value_of<E, E::A, E::B, E::C>(e);  // what's even the point?
}

Giovanni Piero Deretta

unread,
Jun 13, 2016, 5:33:26 AM6/13/16
to ISO C++ Standard - Future Proposals

For what is worth, you can abuse GCC statement expressions plus variadics to get variadic expansion of switch statements. Although 'case' in statement expressions is explicitly documented not to be supported, it does work in practice (nothing I would use in production of course).

So, while I wouldn't call it trivial, it seems to me that it souldn't be too hard to implement the extension in GCC, and you could even consider statement expressions as existing practice.

-- gpd

Edward Catmur

unread,
Jun 13, 2016, 10:14:20 AM6/13/16
to std-pr...@isocpp.org
On Mon, Jun 13, 2016 at 10:33 AM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
On Sunday, June 12, 2016 at 11:25:50 AM UTC+1, Ville Voutilainen wrote:
On 12 June 2016 at 13:23,  <inkwizyt...@gmail.com> wrote:
> This code should be fast as manually written switch, it would be trivial for
> compiler to create jump table to have best performance.


I'm sure the open-source compilers will happily accept an extension
patch that shows how trivial it is.

For what is worth, you can abuse GCC statement expressions plus variadics to get variadic expansion of switch statements. Although 'case' in statement expressions is explicitly documented not to be supported, it does work in practice (nothing I would use in production of course).

That's amazing! I couldn't get it to work in any version after 4.9.2, though; is there a trick I'm missing?

  switch (i) {
    ([](...){})(({ case I : g<I>(), ({ return; }), 0; })... );
  }
 
So, while I wouldn't call it trivial, it seems to me that it souldn't be too hard to implement the extension in GCC, and you could even consider statement expressions as existing practice.

Absolutely; it'd be a nice hack to work on.

Edward Catmur

unread,
Jun 13, 2016, 10:32:33 AM6/13/16
to std-pr...@isocpp.org
On Mon, Jun 13, 2016 at 4:11 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Sun, Jun 12, 2016 at 6:17 PM, Edward Catmur <e...@catmur.co.uk> wrote:
On Sunday, 12 June 2016 20:32:22 UTC+1, Arthur O'Dwyer wrote:
I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.

Too many times to count, I'm sure! I decided to take another go at it, trying to conform to the Boost.Switch interface but using modern techniques, using only the standard library and Boost.Preprocessor for iteration. My implementation is a little under 60 lines: https://gist.github.com/ecatmur/7f5f8b44e70f414742e4eae1efdd0ca7 

Interesting!  My implementation ended up at twice the length of yours, but IMHO has a nicer user interface than Boost.Switch.


The "apply" example in my case looks like this: extra boilerplate to deal with making index sequences, but the tradeoff is that my interface allows you to switch on things that aren't consecutive integers.

Actually, mine can switch on any integer sequence; it uses the preprocessor iteration over N to index into the integer sequence for the case label constant (using std::tuple_element, since that's the only available facility in the standard library for variadic indexing).

The other tradeoff is that my Clang isn't smart enough to turn this into a switch. It will go as far as inlining everything into an if-else chain (no function calls), but it won't make a jump table. Yes, this kind of defeats the purpose; but I'm holding out hope that it's a Quality of Implementation issue rather than a fundamental flaw.

Yes, it's a bit disappointing that the free compilers are unable to turn a recursive if chain into a jump table. Using a syntactic switch has other benefits, though; it allows the compiler to automatically detect repeated cases, and also to detect omitted cases where the controlling type is an enum.
 
The "enum to integer" example in your gist doesn't compile on my Clang because it is trying to instantiate std::integer_sequence<E, ...> where E (being an enum) is not an integral type; I think you have to add some boilerplate involving std::conditional_t<std::is_enum_v<E>, std::underlying_type_t<E>, E> and some explicit casts, at which point it doesn't even seem worth it anymore.

Hm. Clang works fine for me in versions 3.5 upward in -std=c++14 mode.
 
Having to create an entity of the form foo<..., E::A, E::B, E::C> should have been a red flag anyway, IMHO.

That sequence would usually be provided by an introspection library (BetterEnums, or whatever SG7 are up to these days), or by a code generator. 

Giovanni Piero Deretta

unread,
Jun 13, 2016, 11:43:09 AM6/13/16
to std-pr...@isocpp.org


On 13 Jun 2016 3:14 p.m., "'Edward Catmur' via ISO C++ Standard - Future Proposals" <std-pr...@isocpp.org> wrote:
>
> On Mon, Jun 13, 2016 at 10:33 AM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
>>
>> On Sunday, June 12, 2016 at 11:25:50 AM UTC+1, Ville Voutilainen wrote:
>>>
>>> On 12 June 2016 at 13:23,  <inkwizyt...@gmail.com> wrote:
>>> > This code should be fast as manually written switch, it would be trivial for
>>> > compiler to create jump table to have best performance.
>>>
>>>
>>> I'm sure the open-source compilers will happily accept an extension
>>> patch that shows how trivial it is.
>>
>>
>> For what is worth, you can abuse GCC statement expressions plus variadics to get variadic expansion of switch statements. Although 'case' in statement expressions is explicitly documented not to be supported, it does work in practice (nothing I would use in production of course).
>
>
> That's amazing! I couldn't get it to work in any version after 4.9.2, though; is there a trick I'm missing?
>
>   switch (i) {
>     ([](...){})(({ case I : g<I>(), ({ return; }), 0; })... );
>   }
>  

It's been a while; I remember not needing the lambda but I had to split the line in multiple separate statement expressions. It did require a lot of trials and errors to find a formulation that GCC would accept. I remember never getting rerun to work (only break).

Sergey Vidyuk

unread,
Jun 13, 2016, 1:56:48 PM6/13/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com
суббота, 11 июня 2016 г., 1:10:04 UTC+6 пользователь inkwizyt...@gmail.com написал:
 I think that `for constexpr` should be allowed to extracting parameters from templates, some thing like that:
using type = std::tuple<int, long long, char>;

int main()
{
   
for constexpr (typename T : type) //without `...`
   
{
        f
<T>();
   
} //equal: `f<int>(); f<long long>(); f<char>();`
}


Am I right tha the main and only reason you would like to grab template parameters with 'for constexpr' is to have type list outside of template?
 
This will allow usage outside of template function and simplify complex operation:
template<typename T>
using MetaFunc = std::tuple<int, T, T, char>;


int main()
{
   
for constexpr (typename T : MetaFunc<long>)
   
{
        f
<T>();
   
} //equal: `f<int>(); f<long>(); f<long>(); f<char>();`
   
for constexpr (typename T : MetaFunc<char>)
   
{
       
for constexpr (typename TT : MetaFunc<T>)
       
{
           g
<T,TT>();
       
}
   
}
}


 I see one issue with this example. Your MetaFunc is template typedef and instead of iteraating over single element list you are iterating through  4 elements list since you are grabbing parameters of type mapped my template typedef but not parameters of teemplate typedef itself. The simpliest way to have list of types which is never can accidentally create some nonvoid variable is template typedef std::void_t which can be implemented as

template<typename... T>
using void_t = void;

how the following loop should work then?

for constexpr (typename T: void_t<int8_t, int16_t, int32_t, int64_t>) {
    do_something
();
}

My personal opinion on such feature: there should be separate syntax to grab template parameters like

for constexpr (typename T: params_of<void_t<int8_t, int16_t, int32_t, int64_t>>) {
    do_something
();
}

but it require some extra work on how this should actually work. In D it's possible to implement params_of in library code since it's possible to typedef parameters pack and then use it when needed. The following C++ code rewritten in D works as expected:

template<typename... L>
struct test {
   
using params = L;
};

int main() {
   
for constexpr (typename T, test<char, int>::params) {
        std
::cout << sizeof(T) << std::endl;
   
}
}

From my personal point of view this way to have typelist outside of template is much better then grabbing parameters from some intermediate template which is needed only to enlist types you need. This approach also allows to have better of your MetaFunc template without issues I've mentioned above.

Sergey Vidyuk

unread,
Jun 13, 2016, 2:53:58 PM6/13/16
to ISO C++ Standard - Future Proposals
понедельник, 13 июня 2016 г., 1:32:22 UTC+6 пользователь Arthur O'Dwyer написал:

`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); goto finalFor;
   
case Op1::OpId: Op1::OpFunc(); goto finalFor;
   
case Op2::OpId: Op2::OpFunc(); goto finalFor;
   
case Op3::OpId: Op3::OpFunc(); goto finalFor;
    finalFor
: break;
}

No, that doesn't work.  I think Tony has a point.
Sergey's half-proposal wants to make "break" and "continue" inside a "for constexpr" act exactly like "break" and "continue" inside a non-constexpr "for":


Actually I would like break/continue to be turned into jumps to a body of the "end of loop"/"next iteration loop body instantiation" so previous example uf abusing for constexpr to case-expand parameters pack is technically correct. In my third message in this converation I've posted example of function set(obj, property_name, property_val_as_json) {obj.proprty_name = parse<decltype(obj.proprty_name)>(property_val_as_json);} which relies on such behaviour. If break always terminate instantiation of loop body for the rest of parameters and continue stops instantiation of statements bellow then they must be forbidden inside any conditional code when condition is evaluated at runtime. Only to ways to leave constexpr loop by runtime condition remains: explicit goto (ugly) and return (require to move part of your function implementation into some helper function). As far as I can see continue based on runtime condition inside for constexpr will only be implementable by rewriting the code with recursive templates.
 
    for constexpr(typename T : Ts...) {
        do_something<T>();
        if constexpr(is_same_v<T, nullptr_t>) break;
    }

This code would generate an unrolled loop of do_something<T>()s for each T in Ts... up to and including the first instance of nullptr_t. As soon as it generated do_something<nullptr_t>(), it would break and stop iterating over Ts....


I think that in such case compiler should be able to detect unreachable code and cut it. May be this can be required by the for constexpr proposal.

My personal opinion on "constexpr break" (break which stops instantiation of the rest of loop iterations): It's a step to add additional sublanguage of "codegeneration based on constexpr conditions and lists" into C++. There are 3 C++ sublanguages (preprocessor, templates, classic-C++) already and this kind of language fragmentation should be avoided in the future evaluation. Break which is turned into goto integrates into the langage much better.

-- Sergey

Arthur O'Dwyer

unread,
Jun 13, 2016, 2:58:58 PM6/13/16
to ISO C++ Standard - Future Proposals
Sorry, that's impossible in C++.  Intuitively,

    using result = void_t<A,B,C,D>;
    ... some expression involving params_of<result> ...

is a non-starter for the same reason that

    auto sum = a + b + c + d;
    ... some expression involving addends_of<sum> ...

is a non-starter. Alias templates are referentially transparent (at compile time) in the same way that ints are (at runtime); void_t<A,B,C,D> is void as far as the compiler is concerned.  That's an intentional feature; it allows type deduction to "see through" parameters whose types are template aliases whereas type deduction can't "see through" parameters whose types are member typedefs of template classes.

HTH,
Arthur

Sergey Vidyuk

unread,
Jun 13, 2016, 3:00:52 PM6/13/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com
воскресенье, 12 июня 2016 г., 17:13:48 UTC+6 пользователь inkwizyt...@gmail.com написал:
I was referring to switch after applying `for constexpr`. Some thing like that:
switch (x)
{
   
for constexpr (typename T : Op...)
   
{
   
case T::OpId: T::OpFunc(); break;
   
}
   
break;
}
//After `for constexpr` "unroll"
switch (x)
{
   
case Op0::OpId: Op0::OpFunc(); break;
   
case Op1::OpId: Op1::OpFunc(); break;
   
case Op2::OpId: Op2::OpFunc(); break;
   
case Op3::OpId: Op3::OpFunc(); break;
}
And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.


Maybe it's better to have syntax to expand parameters pack into set of case statements. Posibility to mix switch with loops is one of the most confusing and dangerous part of the language. I fill it's better to avoid adding for constexpr as a common practice to unpack parameters pack into cases.

-- Sergey
 

inkwizyt...@gmail.com

unread,
Jun 13, 2016, 7:18:24 PM6/13/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com

No, because type that is pass to `for` is `void` without any parameters. It will behave same if you pass `void_t<int, long>` to template and look what template specialization is chosen. It will be `void`.
 
My personal opinion on such feature: there should be separate syntax to grab template parameters like

for constexpr (typename T: params_of<void_t<int8_t, int16_t, int32_t, int64_t>>) {
    do_something
();
}

but it require some extra work on how this should actually work. In D it's possible to implement params_of in library code since it's possible to typedef parameters pack and then use it when needed. The following C++ code rewritten in D works as expected:

template<typename... L>
struct test {
   
using params = L;
};

int main() {
   
for constexpr (typename T, test<char, int>::params) {
        std
::cout << sizeof(T) << std::endl;
   
}
}

From my personal point of view this way to have typelist outside of template is much better then grabbing parameters from some intermediate template which is needed only to enlist types you need. This approach also allows to have better of your MetaFunc template without issues I've mentioned above.

My idea was that we have concrete template type with some parameters. Then `for constexpr` will extract parameters of this template. This is why template alias are skipped. They can never by concrete. They more like type to type functions that templates.

Sergey Vidyuk

unread,
Jun 14, 2016, 1:02:04 AM6/14/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com


вторник, 14 июня 2016 г., 5:18:24 UTC+6 пользователь inkwizyt...@gmail.com написал

My idea was that we have concrete template type with some parameters. Then `for constexpr` will extract parameters of this template. This is why template alias are skipped. They can never by concrete. They more like type to type functions that templates.

Ok. Got the point on alias templates. But it actually means that this feature is only usable when it's strong guarantie that template you are using not an alias to nontrivial implementation. For example there might be some problems with your examples if std::tuple is defined in such way:

template<typename... L>
using tuple = detail::tuple_impl<sizeof...(L), L...>;

grbbing template by for constexpr seems only be usable together with some template class designed to be used for grabbing parameters. Something like:

template<typename... L>
struct types_list {};

which has strong guarantie that no extra types or nontype argument will apear when it's used in "for constexpr". I think that D-like code I've posted above solves the same issue in much better way:



template<typename... L>
struct test {
   
using params = L;
};

int main() {
   
for constexpr (typename T, test<char, int>::params) {
        std
::cout << sizeof(T) << std::endl;
   
}
}

 
Thre are two different language constructs designed for their own purpuses which allows to have type list to iterate over in nontemplate function when those features are used together. From my personal point of view this way is much better than grabbing template parameters unless there are some use cases which are not just "some way to have iterable typelist in non-template code". Do you have some example usecases for the feature?

-- Sergey

inkwizyt...@gmail.com

unread,
Jun 14, 2016, 3:56:46 PM6/14/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com


On Tuesday, June 14, 2016 at 7:02:04 AM UTC+2, Sergey Vidyuk wrote:


вторник, 14 июня 2016 г., 5:18:24 UTC+6 пользователь inkwizyt...@gmail.com написал

My idea was that we have concrete template type with some parameters. Then `for constexpr` will extract parameters of this template. This is why template alias are skipped. They can never by concrete. They more like type to type functions that templates.

Ok. Got the point on alias templates. But it actually means that this feature is only usable when it's strong guarantie that template you are using not an alias to nontrivial implementation. For example there might be some problems with your examples if std::tuple is defined in such way:

template<typename... L>
using tuple = detail::tuple_impl<sizeof...(L), L...>;

grbbing template by for constexpr seems only be usable together with some template class designed to be used for grabbing parameters. Something like:


Exactly, this is downside but it have upside too. You could transform pack in something else. This will allow push lot of junk out your way:

for constexpr (typename TT : zip_t<take_t<10, std::tuple<T...>>, std::make_index_sequence_t<10>>)
{
   
typename TT::first a; //type from T pack, but only first 10
   
int a[TT::second::value]; //value from make_index_sequence_t
}




 
template<typename... L>
struct types_list {};

which has strong guarantie that no extra types or nontype argument will apear when it's used in "for constexpr". I think that D-like code I've posted above solves the same issue in much better way:


template<typename... L>
struct test {
   
using params = L;
};

int main() {
   
for constexpr (typename T, test<char, int>::params) {
        std
::cout << sizeof(T) << std::endl;
   
}
}

 
Thre are two different language constructs designed for their own purpuses which allows to have type list to iterate over in nontemplate function when those features are used together. From my personal point of view this way is much better than grabbing template parameters unless there are some use cases which are not just "some way to have iterable typelist in non-template code". Do you have some example usecases for the feature?

-- Sergey


Some time ago was couple of discussions about exposing parameters as stand alone entity. One example of it: https://groups.google.com/a/isocpp.org/forum/?fromgroups#!searchin/std-proposals/parameters$20packs/std-proposals/YkHPCYb-KPQ/jqRUQ6f_Tz0J

Reply all
Reply to author
Forward
0 new messages