constexpr-specified loop variable

2,166 views
Skip to first unread message

Andrew Tomazos

unread,
Oct 3, 2013, 10:09:21 AM10/3/13
to std-pr...@isocpp.org
The following is ill-formed:

template<int x> void f() {}

int main()
{
    for (constexpr int x : {1,2,3})
        f<x>();
}

as constexpr cannot specify a loop variable.

Maybe we could say:

for (constexpr T t : L)

is well-formed iff:

constexpr T[] arr = L

is well-formed.  The semantics are as if the loop is unrolled in the obvious way.

So for the original example it is as if:

    int main()
    {
        {
            constexpr int x = 1;
            f<x>();
        }

        {
            constexpr int x = 2;
            f<x>();
        }

        {
            constexpr int x = 3;
            f<x>();
        }

    }

Andrew Tomazos

unread,
Oct 3, 2013, 10:10:30 AM10/3/13
to std-pr...@isocpp.org
Typo:


    constexpr T arr[] = L

Daniel Krügler

unread,
Oct 3, 2013, 10:22:48 AM10/3/13
to std-pr...@isocpp.org
2013/10/3 Andrew Tomazos <andrew...@gmail.com>:
> The following is ill-formed:
>
> template<int x> void f() {}
>
> int main()
> {
> for (constexpr int x : {1,2,3})
> f<x>();
> }
>
> as constexpr cannot specify a loop variable.

This is not true, see [stmt.ranged] p2:

"In the decl-specifier-seq of a for-range-declaration, each
decl-specifier shall be either a type-specifier or
constexpr."

The actual reason for a failure here is that the semantics of the
range-for loop needs to be corrected to further support constexpr.
Note that

auto && __range = range-init;

is not marked as constexpr, so the following usage of constexpr in the
loop is invalid. Fixing this will be a bit tricky, though.

- Daniel

Alex B

unread,
Oct 3, 2013, 10:31:18 AM10/3/13
to std-pr...@isocpp.org
Interesting.
Sounds like something that could be called "static for" ...
Combine this with a restricted "static if" (see this thread: https://groups.google.com/a/isocpp.org/forum/#!searchin/std-proposals/imperative/std-proposals/bX96720ccmU/_lAHMoLI224J) and template programming suddenly becomes quite similar to regular programming...

inkwizyt...@gmail.com

unread,
Oct 3, 2013, 12:40:50 PM10/3/13
to std-pr...@isocpp.org
I recently suggest similar thing but focused on variadic templates where it would be more useful:
template<typename... T>
void func()
{
   
for...(Ti : T) //"loop" through type pack
   
{
       
/*code there*/
   
}
}
`for...` is analogy to `sizeof...`.
Another important thing it need to ignore `break` and `continue`. This is require to allow integrate with normal loops or switch statement:
switch(i)
{
   
for...(Ti : T)
   
{
   
case Ti::value:
       
Ti::f();
       
break; //break for `switch` not `for...`
   
}
}

Another thing, `for...(` is shorter than `static for(` or `for(constexpr`.




On Thursday, October 3, 2013 4:09:21 PM UTC+2, Andrew Tomazos wrote:

Andrew Tomazos

unread,
Oct 3, 2013, 12:41:47 PM10/3/13
to std-pr...@isocpp.org

Yes, correct, that is what I meant  We could add a constraint on 6.5.4 [stmt.ranged] "The range-based for statement", that states the for-range-declaration must not be constexpr, and then define:

6.5.5 The unrolled for statement [stmt.unrolled]

For a for statement of the form

    for ( for-range-declaration : expression ) statement

where the for-range-declaration has a constexpr specifier, the statement is known as an unrolled for statement

let range-init, and begin-expr be defined the same as a range-based for statement

An unrolled for statement is equivalent to:

    {
        constexpr decltype(*begin-expr) __range[] = range-init;
        constexpr std::size_t __extent = std::extent<decltype(__range)>::value;
        {
             for-range-declaration = __range[0];
             statement
        }
        {
             for-range-declaration = __range[1];
             statement
        }
        {
             for-range-declaration = __range[2];
             statement
        }
        /* ... */
        {
              for-range-declaration = __range[__extent-1];
             statement
        }
    }

Where __range and __extent are variables for exposition only.

Alex B

unread,
Oct 3, 2013, 1:44:32 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 12:40 PM, <inkwizyt...@gmail.com> wrote:
I recently suggest similar thing but focused on variadic templates where it would be more useful:
template<typename... T>
void func()
{
   
for...(Ti : T) //"loop" through type pack
   
{
       
/*code there*/
   
}
}
`for...` is analogy to `sizeof...`.
Another important thing it need to ignore `break` and `continue`. This is require to allow integrate with normal loops or switch statement:
switch(i)
{
   
for...(Ti : T)
   
{
   
case Ti::value:
       
Ti::f();
       
break; //break for `switch` not `for...`
   
}
}

Another thing, `for...(` is shorter than `static for(` or `for(constexpr`.

 
Interesting as well. The downside about your solution is that it requires a pack. So for example, if I want to call a funcion f over all the elements of a tuple-like class (that could be tuple, array or pair), I will need to dispatch to an extra function taking an index_sequence:

template <class TupleLike, std::size_t... Seq>
void _func_impl(TupleLike&& t, std::index_sequence<Seq...>)
{
   for... (I : Seq)
      f(std::get<I>(std::forward<TupleLike>(t)));
}

template <class TupleLike>
void func(TupleLike&& t)
{
   _func_impl(
      std::forward<TupleLike>(t),
      std::make_index_sequence<std::tuple_size<std::remove_reference_t<TupleLike>>::value >{}
   );
}


However that might not be a burden in the future if there is a way to "generate a pack" and/or overload operator... (see this very interesting thread: https://groups.google.com/a/isocpp.org/forum/#!starred/std-proposals/g1HziHt1lbo).

template <class TupleLike>
void func(TupleLike&& t)
{
   for... (v : std::forward<TupleLike>(t))
      f(v);
}

Andrew Tomazos

unread,
Oct 3, 2013, 2:38:12 PM10/3/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
On Thursday, October 3, 2013 6:40:50 PM UTC+2, inkwizyt...@gmail.com wrote:
I recently suggest similar thing but focused on variadic templates where it would be more useful:
template<typename... T>
void func()
{
   
for...(Ti : T) //"loop" through type pack
   
{
       
/*code there*/
   
}
}

For non-type template parameter packs unrolled for statements work just fine:

    template<int... X>
    void func()
    {
        for (constexpr auto x : {X...})
            g<x>();
    }

For type template parameter packs it doesn't currently work yet, this is a good thought.  I would suggest extending unrolled for statements to allow the typedef specifier and a type-init-list (a braced-init-list of type-ids):

    template<typename... Args>
    void func()
    {
        for (typedef T : {Args...})
            g<T>();
    }

The semantics are clear.  The declaration at the start of each block becomes a typedef of the corresponding type.

Another important thing it need to ignore `break` and `continue`.

I think break and continue should have their usual expected semantics.  break jumps to the end of the last unrolled block.  continue jumps to the start of the next one.

 
> This is require to allow integrate with normal loops or switch statement:
switch(i)
{
   
for...(Ti : T)
   
{
   
case Ti::value:
       
Ti::f();
       
break; //break for `switch` not `for...`
   
}
}


That's not necessary.  You can do what you want as follows:

    for (typedef Ti : {T...})
        if (i == Ti::value)
        {
            Ti::f();
            break;
        }

The optimizer will notice the pattern and then generate code as fast as a switch statement.
 
Another thing, `for...(` is shorter than `static for(` or `for(constexpr`.

I think that using the constexpr and typedef specifiers are better choices, given that they already have exactly the right semantic meaning.

Alex B

unread,
Oct 3, 2013, 3:31:02 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 2:38 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
I would suggest extending unrolled for statements to allow the typedef specifier and a type-init-list (a braced-init-list of type-ids):

    template<typename... Args>
    void func()
    {
        for (typedef T : {Args...})
            g<T>();
    }

The semantics are clear.  The declaration at the start of each block becomes a typedef of the corresponding type.

Maybe "using" instead of "typedef"? Usually, what follow typedef is the name of the original type, not the alias.
typedef int T;
using T = int; // closer to what you want

Also, having a list of types delimited by {} is a bit weird and it might be complicated to make it work...


Another important thing it need to ignore `break` and `continue`.

I think break and continue should have their usual expected semantics.  break jumps to the end of the last unrolled block.  continue jumps to the start of the next one.

 
> This is require to allow integrate with normal loops or switch statement:
switch(i)
{
   
for...(Ti : T)
   
{
   
case Ti::value:
       
Ti::f();
       
break; //break for `switch` not `for...`
   
}
}



I think I looked at that sample too quickly; I agree as well that break and continue should not have special rules. Your "for..." should really behave like a regular "for" in term of scope as well. That debate is going in the same direction as the "static if" debate: should it introduce a new scope? Consensus so far is: definitely.

Of course, that would break the switch example...

inkwizyt...@gmail.com

unread,
Oct 3, 2013, 4:15:30 PM10/3/13
to std-pr...@isocpp.org
Yes, it should have scope, and will not break switch because switch allow loops (and scopes) inside: http://en.wikipedia.org/wiki/Duff%27s_device (and it should work in C++ at least in g++ it still works).

Richard Smith

unread,
Oct 3, 2013, 4:34:12 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 7:09 AM, Andrew Tomazos <andrew...@gmail.com> wrote:
The following is ill-formed:

template<int x> void f() {}

int main()
{
    for (constexpr int x : {1,2,3})
        f<x>();
}

as constexpr cannot specify a loop variable.

It's not true that we can't use 'constexpr' to specify a loop variable. Hint:

struct my_iterator {
  // ...
  constexpr operator int() { return 0; }
  // ...
};

(It's not very useful, because the value of the loop variable can't depend on the range, but it is possible.)

The only reason we allow 'constexpr' in this position is so that the syntax of range-based and non-range-based for loops are identical up until we hit the ':'. See DR1204.

Maybe we could say:

for (constexpr T t : L)

is well-formed iff:

constexpr T[] arr = L

is well-formed.  The semantics are as if the loop is unrolled in the obvious way.

This seems like a strange approach to me. If you want to force loop unrolling, it would seem much more natural to use a pack expansion. Using a construct like 'for' would violate peoples' intuitions ('for' does not usually imply code duplication, and a 'constexpr' variable declaration can usually have only a single value).

In discussions with members of the reflection SG, a syntax like:

f<...{1, 2, 3}>() ...;

was discussed and had some support (here, ...{1, 2, 3} is a pack literal, and ...; indicates a statement pack expansion).

So for the original example it is as if:

    int main()
    {
        {
            constexpr int x = 1;
            f<x>();
        }

        {
            constexpr int x = 2;
            f<x>();
        }

        {
            constexpr int x = 3;
            f<x>();
        }

    }

--
 
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Andrew Tomazos

unread,
Oct 3, 2013, 6:56:23 PM10/3/13
to std-pr...@isocpp.org
On Thursday, October 3, 2013 10:34:12 PM UTC+2, Richard Smith wrote:
It's not true that we can't use 'constexpr' to specify a loop variable.

Yes, as per my followup to Daniel I meant that it isn't effectively possible because of how range-based for is currently defined.

The semantics are as if the loop is unrolled in the obvious way.

If you want to force loop unrolling, it would seem much more natural to use a pack expansion.
 
The purpose isn't to force unrolling, the term "unrolled" is purely for specification purposes.  An implementation is free to unroll a normal loop, and is free to rollup an "unrolled" loop.  It's purely the logical behaviour of the program that is specified.

Using a construct like 'for' would violate peoples' intuitions.  'for' does not usually imply code duplication,
 
The behaviour of a program containing:

      for (T x : L)

and the same one instead containing:

    for (constexpr T x : L)

are identical if both well-formed.  So it isn't clear how it is unintuitive.  As stated the code duplication is only logical.


a 'constexpr' variable declaration can usually have only a single value

In the following:

    template<int x>
    constexpr int g()
    {
        constexpr int y = x*2;

        return y + 5;
    }

    constexpr int z1 = g<3>();
    constexpr int z2 = g<4>();

The `y` constexpr variable declaration has two different values, 6 and 8.

Just like in the following:

    for (constexpr int x : {3,4})
    {
        constexpr int y = x*2;

        /* ... */
    }

We can think of the inner statement of the unrolled for statement as if it were the body of a unnamed template function, and the loop variable as a non-type template parameter.  The body is instantiated and called once for each element of the constexpr array initializer.

Richard Smith

unread,
Oct 3, 2013, 7:17:06 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 3:56 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
On Thursday, October 3, 2013 10:34:12 PM UTC+2, Richard Smith wrote:
It's not true that we can't use 'constexpr' to specify a loop variable.

Yes, as per my followup to Daniel I meant that it isn't effectively possible because of how range-based for is currently defined.

It's *not* effectively impossible. To reiterate: you can pick a range type such that the code is valid. It's just not very useful.

The semantics are as if the loop is unrolled in the obvious way.

If you want to force loop unrolling, it would seem much more natural to use a pack expansion.
 
The purpose isn't to force unrolling, the term "unrolled" is purely for specification purposes.  An implementation is free to unroll a normal loop, and is free to rollup an "unrolled" loop.  It's purely the logical behaviour of the program that is specified.

No, this is something different. You want the static types of elements of the loop body to be different on different iterations, for instance. That *forces* the loop to be fully unrolled.
 
Using a construct like 'for' would violate peoples' intuitions.  'for' does not usually imply code duplication,
 
The behaviour of a program containing:

      for (T x : L)

and the same one instead containing:

    for (constexpr T x : L)

are identical if both well-formed.  So it isn't clear how it is unintuitive.  As stated the code duplication is only logical.

See above.
 
a 'constexpr' variable declaration can usually have only a single value

In the following:

    template<int x>
    constexpr int g()
    {
        constexpr int y = x*2;

        return y + 5;
    }

    constexpr int z1 = g<3>();
    constexpr int z2 = g<4>();

The `y` constexpr variable declaration has two different values, 6 and 8.

Not in a single fully-instantiated function.
 
Just like in the following:

    for (constexpr int x : {3,4})
    {
        constexpr int y = x*2;

        /* ... */
    }

We can think of the inner statement of the unrolled for statement as if it were the body of a unnamed template function,

Right. Making it act like a template is the thing that's new and unintuitive.
 
and the loop variable as a non-type template parameter.  The body is instantiated and called once for each element of the constexpr array initializer.

Andrew Tomazos

unread,
Oct 3, 2013, 8:20:46 PM10/3/13
to std-pr...@isocpp.org
On Friday, October 4, 2013 1:17:06 AM UTC+2, Richard Smith wrote:
On Thu, Oct 3, 2013 at 3:56 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
Yes, as per my followup to Daniel I meant that it isn't effectively possible because of how range-based for is currently defined.

It's *not* effectively impossible. To reiterate: you can pick a range type such that the code is valid. It's just not very useful.
 
Yes, fine.  We are in violent agreement that a constexpr-specified loop variable is currently impossible to use in a very useful way.

The purpose isn't to force unrolling, the term "unrolled" is purely for specification purposes.  An implementation is free to unroll a normal loop, and is free to rollup an "unrolled" loop.  It's purely the logical behaviour of the program that is specified.

No, this is something different. You want the static types of elements of the loop body to be different on different iterations, for instance. That *forces* the loop to be fully unrolled.

In some cases, such as when there are varying static types dependant on the loop variable it may force the loop to be unrolled, but this would be a consequence of the intention of the code, and not grounds to claim the construct is unintuitive.

 
We can think of the inner statement of the unrolled for statement as if it were the body of a unnamed template function,

Right. Making it act like a template is the thing that's new and unintuitive.
 
I think you may be conflating the unintuitive with the difficult-to-implement.

Richard Smith

unread,
Oct 3, 2013, 9:05:17 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 5:20 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
On Friday, October 4, 2013 1:17:06 AM UTC+2, Richard Smith wrote:
On Thu, Oct 3, 2013 at 3:56 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
Yes, as per my followup to Daniel I meant that it isn't effectively possible because of how range-based for is currently defined.

It's *not* effectively impossible. To reiterate: you can pick a range type such that the code is valid. It's just not very useful.
 
Yes, fine.  We are in violent agreement that a constexpr-specified loop variable is currently impossible to use in a very useful way.

The purpose isn't to force unrolling, the term "unrolled" is purely for specification purposes.  An implementation is free to unroll a normal loop, and is free to rollup an "unrolled" loop.  It's purely the logical behaviour of the program that is specified.

No, this is something different. You want the static types of elements of the loop body to be different on different iterations, for instance. That *forces* the loop to be fully unrolled.

In some cases, such as when there are varying static types dependant on the loop variable it may force the loop to be unrolled, but this would be a consequence of the intention of the code, and not grounds to claim the construct is unintuitive.

It's a consequence of the new semantics you'd be giving to the construct, so this is a property of the construct itself, not of the code using it.
 
We can think of the inner statement of the unrolled for statement as if it were the body of a unnamed template function,

Right. Making it act like a template is the thing that's new and unintuitive.
 
I think you may be conflating the unintuitive with the difficult-to-implement.

I was addressing this thread from the point of view of a user of C++, but speaking now as a compiler implementer, I disagree. I don't *want* to implement this, because I think it's a bad syntax, but it's not really difficult, just weird, surprising, non-uniform, and inelegant.

The argument for this particular syntax seems to basically be "there's a hole in the grammar here which we can jam something new into" rather than "this is a natural extension of the existing syntax". That's a bad argument. Using the "constexpr" keyword to mean "implicitly treat the body of this loop like it's a template" is new and unintuitive.

That said, I think the problem is worth solving. And we already have a tool to repeatedly stamp out multiple, slightly different, copies of the same construct: pack expansion. That's the direction that I think should be pursued here. Plus, any improvements there (such as allowing pack-expansion of expression-statements, or providing pack literals) address a wide variety of problems and fit nicely with existing language constructs, rather than being something new and special-purpose.

Alex B

unread,
Oct 3, 2013, 10:40:50 PM10/3/13
to std-pr...@isocpp.org
On Thu, Oct 3, 2013 at 4:15 PM, <inkwizyt...@gmail.com> wrote:
Yes, it should have scope, and will not break switch because switch allow loops (and scopes) inside: http://en.wikipedia.org/wiki/Duff%27s_device (and it should work in C++ at least in g++ it still works).


Nice example; I have to admit that I did not know it was valid to declare case labels in inner scopes.
I think that the "for..." syntax that you propose is a nice way to solve the problem. Like Richard said, dealing with packs to solve this problem is probably the right thing to do. I like the fact that your proposed syntax allow you to choose which pack is expanded.

The one thing I still do not agree however is about break and continue being ignored. It looks like you want to make it this way to make your specific example work. Most programmers would expect break and continue to apply directly to the "for..." scope (and sometime they would actually need it).

Maybe you should look for an alternate solution to make your break statement work on the switch. I saw a few suggestions about it on this forum, like writing break twice ("break break;") or specifying an integer to the break statement ("break(2);"). It's an old and recurrent problem that might be worth solving in a generic way (not just by saying "ignore break in this context").

Andrew Tomazos

unread,
Oct 4, 2013, 6:01:49 AM10/4/13
to std-pr...@isocpp.org
On Friday, October 4, 2013 3:05:17 AM UTC+2, Richard Smith wrote:
That said, I think the problem is worth solving. And we already have a tool to repeatedly stamp out multiple, slightly different, copies of the same construct: pack expansion. That's the direction that I think should be pursued here. Plus, any improvements there (such as allowing pack-expansion of expression-statements, or providing pack literals) address a wide variety of problems and fit nicely with existing language constructs, rather than being something new and special-purpose.

Ok, if your objections don't come from implementation-difficulty, then I don't think we are miles apart here:

How about the following "generic for statement" syntax:

    for <template-parameter : template-argument-list>
        statement

This expands to:

    template<template-parameter>
    void __body
    {
          statement
    }

    template<template-parameter... pack>
    void __repeat_body
    {
        __body<pack>()...;
    }

    // callsite
    __repeat_body<template-argument-list>()

Example usage:

        void f()
        {
            for <typename T : int, double, complex<float>>
            {
                 T x = f(0);
                 T y = x*x;
                 g(y);
            }

            for <int x : 1,2,3>
                 h<x> = X<x>::value;
        };

The main property I am interested in are that the template parameter, template argument list and inner statement can be defined locally at call site, without having to manually create two single-use named functions (the first to accept the template argument list as a parameter pack, the second to accept the template argument).  You want to keep all of it together in one place, and not have to lexically jump around through names from different scopes - in the same spirit as a lambda or any statement that has a sub-statement.

Discovery should go as usual left-to-range:

1. What is the construct `for<`
2. What is the parameter `template-parameter`
3. Over what list of things are we varying the parameter `: template-argument-list`
4. What are we generating with the parameter: `statement`

If you don't like the for keyword by all means suggest another syntax, but I think the problem is worth solving and the semantics of the construct are worth having.

Is this closer to something you could get behind?

inkwizyt...@gmail.com

unread,
Oct 4, 2013, 1:59:55 PM10/4/13
to std-pr...@isocpp.org
This specific example is reason why I want add this syntax. I treat this more like "copypaste" than normal loop.
Syntax like `break break;` or `break continue;` would be best compromise. But even if this wont pass and `foo...` will ignore (or not) `break` and `continue` we can still use `goto` to work around it.
This bring me to conclusion that `for...` should behave like you want, because one `goto` is small price to pay for possibles that `for...` give.

And when we speaking about `goto`, labels inside `for...` shloud be treat as private, you wont be able jump to them from outside of `for...` scope.
Its simply because every label in `for...` is "copied" multiple times. Sometimes it can be no label at all if pack was empty.

inkwizyt...@gmail.com

unread,
Oct 4, 2013, 2:34:19 PM10/4/13
to std-pr...@isocpp.org
Using `<>` is probably to drastic, is any language in C family that use them in similar fashion? D even replace them in templates (you write `Type!(A, B)` if you want something like `Type<A, B>`).

Andrew Tomazos

unread,
Oct 4, 2013, 4:11:25 PM10/4/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
Angle brackets delimit template argument lists and template parameter lists in C++.  The purpose of using them in the construct, apart from consistency with that clear precedent, is to address Richards concern that the user may not realize it is a generic for loop, and as such implies code duplication when types vary significantly between loops.

inkwizyt...@gmail.com

unread,
Oct 4, 2013, 6:05:02 PM10/4/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
My question was probably not accurate, my point was that this will be first time when angle brackets are used as part flow control syntax in C-family languages.
I would prefer `static for`  or my `for...` to distinguish both usages and leave round brackets.
Another problem is that syntax cant be used directly in macros because it treat them as operators not brackets (its already problem with templates, expanding it isnt probably good idea).
Reply all
Reply to author
Forward
0 new messages