42

If I want to do something like iterate over a tuple, I have to resort to crazy template metaprogramming and template helper specializations. For example, the following program won't work:

#include <iostream>
#include <tuple>
#include <utility>

constexpr auto multiple_return_values()
{
    return std::make_tuple(3, 3.14, "pi");
}

template <typename T>
constexpr void foo(T t)
{
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i)
    {
        std::get<i>(t);
    }    
}

int main()
{
    constexpr auto ret = multiple_return_values();
    foo(ret);
}

Because i can't be const or we wouldn't be able to implement it. But for loops are a compile-time construct that can be evaluated statically. Compilers are free to remove it, transform it, fold it, unroll it or do whatever they want with it thanks to the as-if rule. But then why can't loops be used in a constexpr manner? There's nothing in this code that needs to be done at "runtime". Compiler optimizations are proof of that.

I know that you could potentially modify i inside the body of the loop, but the compiler can still be able to detect that. Example:

// ...snip...

template <typename T>
constexpr int foo(T t)
{
    /* Dead code */
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i)
    {
    }    
    return 42;
}

int main()
{
    constexpr auto ret = multiple_return_values();
    /* No error */
    std::array<int, foo(ret)> arr;
}

Since std::get<>() is a compile-time construct, unlike std::cout.operator<<, I can't see why it's disallowed.

jww
  • 97,681
  • 90
  • 411
  • 885
user6416815
  • 449
  • 1
  • 4
  • 3
  • 2
    Err...for loops don't happen at compile-time. They have to be emitted into the executable by the compiler where they then are executed at ***runtime***. – uh oh somebody needs a pupper Jun 02 '16 at 21:06
  • 4
    In general, for loops can't be compile -time evaluated. You're talking about a very specific case. But the same argument could then be used for just about any construct, not just for loops. – Oliver Charlesworth Jun 02 '16 at 21:10
  • Well, loops may be _kinda_ a compile-time expression when the compiler decides to unroll a loop – ForceBru Jun 02 '16 at 21:11
  • 2
    The statement `std::get(t);` has no effect. Once you put sensible code in there, the problems with your proposal ought to become a lot clearer. – Kerrek SB Jun 02 '16 at 21:20
  • 1
    The idea behind a a compile-time expression is that it is supposed to be an *expression*. `for`-loops are statements that mutate state. If the goal is to get a final value, then a recursive expression should be available. – MicroVirus Jun 02 '16 at 21:24
  • 1
    `D` language's `foreach` can be used at compile time. – coyotte508 Jun 02 '16 at 21:28
  • @coyotte508 All those happy `D` coders, what a pity. – πάντα ῥεῖ Jun 02 '16 at 21:47
  • 16
    There's no conceptual problem with "constexpr for", it just isn't in the language (yet). – M.M Jun 02 '16 at 22:24
  • 1
    @user6416815 see http://stackoverflow.com/questions/8308812/why-is-c11-constexpr-so-restrictive and [this specific answer](http://stackoverflow.com/a/17710588/835629). – coyotte508 Jun 02 '16 at 22:25
  • https://youtu.be/AgatxxXNwBM minute 47:44 about new `for...` keyword. – Patrick Fromberg Jul 22 '19 at 22:03

6 Answers6

19

Here's a way to do it that does not need too much boilerplate, inspired from http://stackoverflow.com/a/26902803/1495627 :

template<std::size_t N>
struct num { static const constexpr auto value = N; };

template <class F, std::size_t... Is>
void for_(F func, std::index_sequence<Is...>)
{
  using expander = int[];
  (void)expander{0, ((void)func(num<Is>{}), 0)...};
}

template <std::size_t N, typename F>
void for_(F func)
{
  for_(func, std::make_index_sequence<N>());
}

Then you can do :

for_<N>([&] (auto i) {      
  std::get<i.value>(t); // do stuff
});

If you have a C++17 compiler accessible, it can be simplified to

template <class F, std::size_t... Is>
void for_(F func, std::index_sequence<Is...>)
{
  (func(num<Is>{}), ...);
}
Jean-Michaël Celerier
  • 7,412
  • 3
  • 54
  • 75
18

πάντα ῥεῖ gave a good and useful answer, I would like to mention another issue though with constexpr for.

In C++, at the most fundamental level, all expressions have a type which can be determined statically (at compile-time). There are things like RTTI and boost::any of course, but they are built on top of this framework, and the static type of an expression is an important concept for understanding some of the rules in the standard.

Suppose that you can iterate over a heterogenous container using a fancy for syntax, like this maybe:

std::tuple<int, float, std::string> my_tuple;
for (const auto & x : my_tuple) {
  f(x);
}

Here, f is some overloaded function. Clearly, the intended meaning of this is to call different overloads of f for each of the types in the tuple. What this really means is that in the expression f(x), overload resolution has to run three different times. If we play by the current rules of C++, the only way this can make sense is if we basically unroll the loop into three different loop bodies, before we try to figure out what the types of the expressions are.

What if the code is actually

for (const auto & x : my_tuple) {
  auto y = f(x);
}

auto is not magic, it doesn't mean "no type info", it means, "deduce the type, please, compiler". But clearly, there really need to be three different types of y in general.

On the other hand, there are tricky issues with this kind of thing -- in C++ the parser needs to be able to know what names are types and what names are templates in order to correctly parse the language. Can the parser be modified to do some loop unrolling of constexpr for loops before all the types are resolved? I don't know but I think it might be nontrivial. Maybe there is a better way...

To avoid this issue, in current versions of C++, people use the visitor pattern. The idea is that you will have an overloaded function or function object and it will be applied to each element of the sequence. Then each overload has its own "body" so there's no ambiguity as to the types or meanings of the variables in them. There are libraries like boost::fusion or boost::hana that let you do iteration over heterogenous sequences using a given vistior -- you would use their mechanism instead of a for-loop.

If you could do constexpr for with just ints, e.g.

for (constexpr i = 0; i < 10; ++i) { ... }

this raises the same difficulty as heterogenous for loop. If you can use i as a template parameter inside the body, then you can make variables that refer to different types in different runs of the loop body, and then it's not clear what the static types of the expressions should be.

So, I'm not sure, but I think there may be some nontrivial technical issues associated with actually adding a constexpr for feature to the language. The visitor pattern / the planned reflection features may end up being less of a headache IMO... who knows.


Let me give another example I just thought of that shows the difficulty involved.

In normal C++, the compiler knows the static type of every variable on the stack, and so it can compute the layout of the stack frame for that function.

You can be sure that the address of a local variable won't change while the function is executing. For instance,

std::array<int, 3> a{{1,2,3}};
for (int i = 0; i < 3; ++i) {
    auto x = a[i];
    int y = 15;
    std::cout << &y << std::endl;
}

In this code, y is a local variable in the body of a for loop. It has a well-defined address throughout this function, and the address printed by the compiler will be the same each time.

What should be the behavior of similar code with constexpr for?

std::tuple<int, long double, std::string> a{};
for (int i = 0; i < 3; ++i) {
    auto x = std::get<i>(a);
    int y = 15;
    std::cout << &y << std::endl;
}

The point is that the type of x is deduced differently in each pass through the loop -- since it has a different type, it may have different size and alignment on the stack. Since y comes after it on the stack, that means that y might change its address on different runs of the loop -- right?

What should be the behavior if a pointer to y is taken in one pass through the loop, and then dereferenced in a later pass? Should it be undefined behavior, even though it would probably be legal in the similar "no-constexpr for" code with std::array showed above?

Should the address of y not be allowed to change? Should the compiler have to pad the address of y so that the largest of the types in the tuple can be accommodated before y? Does that mean that the compiler can't simply unroll the loops and start generating code, but must unroll every instance of the loop before-hand, then collect all of the type information from each of the N instantiations and then find a satisfactory layout?

I think you are better off just using a pack expansion, it's a lot more clear how it is supposed to be implemented by the compiler, and how efficient it's going to be at compile and run time.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • for is definitely doable in constexpr, its by standard – Sergei Krivonos Dec 21 '17 at 16:04
  • 1
    @Sergei: on the contrary, what OP proposes, where the loop variable can be used as a template parameter in the body of for loop, is not doable in `constexpr` by the standard – Chris Beck Dec 21 '17 at 17:12
  • I meant the problem is by standard. Not implemented by design. Possible implementations are: 1) compile and launch code at compile time 2) instantiate templates for each iteration – Sergei Krivonos Dec 21 '17 at 18:43
  • 2
    `for (constexpr i` if it was possible, can simply be unrolled at compile time, ie the loop dissolves into generated code with potentially entirely different code per iteration. Can't believe nobody proposed it yet. Looping over variadic arguments and tuples shouldn't be as horribly painful as it is atm. – rustyx Feb 27 '19 at 11:38
  • 1
    @rustyx if that is how it will work, then in my example, the address of `y` will be different at every iteration. How will debuggers like gdb cope with this? How will we emit debug info? Will we emit debug info `n` times for the variable `y` if there are `n` iterations of the loop? What if it's a `static int y`, what should be the behavior? Should there really be `n` different `y`'s or just one? there's a lot of complexity here, it's not enough to just say "unroll at compile time" because C++ has a ton of features, all of which can appear in a for loop. – Chris Beck Jul 24 '19 at 22:37
  • 1
    If hana::for_each is doable, constexpr for is doable. – chila Apr 29 '20 at 10:28
  • Yes -- it is doable with the syntax and semantics used in hana::for_each :) In hana for_each, the loop variable does not have a single static type, instead its an argument to a lambda that can be different on each iteration. that's how you bypass this problem. – Chris Beck Apr 29 '20 at 15:43
  • I mean you could choose to specify that "`constexpr for` is syntactic sugar for a tuple and a lambda, and the compiler unwinds the sugar at compile-time", but thats going to impose a lot of constraints on the `for( ... ; ... ; ...)` parts, and it's going to look very different when you actually work with such code. Why try to make it look exactly like a for loop when it's actually completely different? – Chris Beck Apr 30 '20 at 07:11
  • 1
    @ChrisBeck "Will we emit debug info n times for the variable y if there are n iterations of the loop?" Yes, of course. "Should there really be n different y's ... ?" Yes of course. There is simply no problem here: it's exactly the same as having an overloaded `template ` function which recursively calls itself to perform the loop (or some similar variant), which is what you currently have to do anyway. `static` variables would follow the same rules i.e. one per iteration, but I agree that is a bit subtle. – Arthur Tacca Aug 19 '20 at 09:44
9

In C++20 most of the std::algorithm functions will be constexpr. For example using std::transform, many operations requiring a loop can be done at compile time. Consider this example calculating the factorial of every number in an array at compile time (adapted from Boost.Hana documentation):

#include <array>
#include <algorithm>

constexpr int factorial(int n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

template <typename T, std::size_t N, typename F>
constexpr std::array<std::result_of_t<F(T)>, N>
transform_array(std::array<T, N> array, F f) {
    auto array_f = std::array<std::result_of_t<F(T)>, N>{};
    // This is a constexpr "loop":
    std::transform(array.begin(), array.end(), array_f.begin(), [&f](auto el){return f(el);});
    return array_f;
}

int main() {
    constexpr std::array<int, 4> ints{{1, 2, 3, 4}};
    // This can be done at compile time!
    constexpr std::array<int, 4> facts = transform_array(ints, factorial);
    static_assert(facts == std::array<int, 4>{{1, 2, 6, 24}}, "");
}

See how the array facts can be computed at compile time using a "loop", i.e. an std::algorithm. At the time of writing this, you need an experimental version of the newest clang or gcc release which you can try out on godbolt.org. But soon C++20 will be fully implemented by all the major compilers in the release versions.

Romeo Valentin
  • 1,276
  • 1
  • 16
  • 22
3

This proposal "Expansion Statements" is interesting and I will provide the link for you to read further explanations.

Click this link

The proposal introduced the syntactic sugar for... as similar to the sizeof... operator. for... loop statement is a compile-time expression which means it has nothing to do in the runtime.

For example:

std::tuple<int, float, char> Tup1 {5, 3.14, 'K'};
for... (auto elem : Tup1) {
     std::cout << elem << " "; 
}

The compiler will generate the code at the compile-time and this is the equivalence:

std::tuple<int, float, char> Tup1 {5, 3.14, 'K'};
{
  auto elem = std::get<0>(Tup1);
  std::cout << elem << " ";
}
{
  auto elem = std::get<1>(Tup1);
  std::cout << elem << " ";
}
{
  auto elem = std::get<2>(Tup1);
  std::cout << elem << " ";
}

Thus, the expansion statement is not a loop but a repeated version of the loop body as it was said in the document.

Since this proposal isn't in C++'s current version or in the technical specification (if it's accepted). We can use the alternative version from the boost library specifically <boost/hana/for_each.hpp> and use the tuple version of boost from <boost/hana/tuple.hpp>. Click this link.

#include <boost/hana/for_each.hpp>
#include <boost/hana/tuple.hpp>
using namespace boost;

...

hana::tuple<int, std::string, float> Tup1 {5, "one", 5.55};
hana::for_each(Tup1, [](auto&& x){
    std::cout << x << " ";
});

// Which will print:
// 5 "one" 5.55

The first argument of boost::hana::for_each must be a foldable container.

Desmond Gold
  • 1,517
  • 1
  • 7
  • 19
0

Why isn't a for-loop a compile-time expression?

Because a for() loop is used to define runtime control flow in the c++ language.

Generally variadic templates cannot be unpacked within runtime control flow statements in c++.

 std::get<i>(t);

cannot be deduced at compile time, since i is a runtime variable.

Use variadic template parameter unpacking instead.


You might also find this post useful (if this not even remarks a duplicate having answers for your question):

iterate over tuple

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 27
    You're just saying "because it is that way". You haven't actually answered the question. – Veedrac Jun 02 '16 at 21:11
  • 7
    @Veedrac: but that is essentially the answer - is because it's the way the language is defined. – Oliver Charlesworth Jun 02 '16 at 21:12
  • 1
    @Veedrac Well, what else do you think I should answer? `for()` isn't designed for compile time constructs, and will never be, period. – πάντα ῥεῖ Jun 02 '16 at 21:13
  • 1
    @Veedrac That answer basically boils down to "because it is that way" though. – uh oh somebody needs a pupper Jun 02 '16 at 21:16
  • 4
    @OliverCharlesworth Of course it's because that's how the language is defined. But that's a useless comment because almost any question can be dismissed as "because that's how the language is defined". The question is about why the language is defined that way, and what rules make it so. – Veedrac Jun 02 '16 at 21:16
  • 1
    @Veedrac Nothing to do with the purpose and workings of `for()` at all. You may live in dreams, but they'll never become true. – πάντα ῥεῖ Jun 02 '16 at 21:16
  • 1
    @sleeptightpupper With the crucial distinction that it explains why. Aka. if someone asked why signed overflow is undefined, saying "because it's what the spec says" is useless without a *why*. – Veedrac Jun 02 '16 at 21:18
  • IOW, your argument could be equally applied to a case where it's wrong. Aka. "x : y ? z / recursion / etc. is used to defined runtime control flow ergo is banned". It offers no *discriminatory power*, ergo has zero entropy. – Veedrac Jun 02 '16 at 21:20
  • 3
    @Veedrac No, it's pure speculation. Saying "you'd have to ask the standards committee why" is the same as "because that's the way it is". – uh oh somebody needs a pupper Jun 02 '16 at 21:20
  • @Veedrac They talk confused sir. – πάντα ῥεῖ Jun 02 '16 at 21:23
  • @sleeptightpupper lol I linked to the wrong post. [I meant this one.](http://stackoverflow.com/a/8309141/1763356) – Veedrac Jun 02 '16 at 21:23
  • 1
    Too be honest I don't think it can be considered a duplicate... The question is asking "why", not "how can I do this", OP already knows how to metaprogram it. And [here](http://stackoverflow.com/questions/8308812/why-is-c11-constexpr-so-restrictive) we see that `constexpr` lost part of its restrictions (used to be a single statement, now we can use if/else), it's not a dream to imagine that sometime we can also use for in it. – coyotte508 Jun 02 '16 at 21:35
  • 1
    @coyotte508 The problem is not `constexpr`. The issue will still be the same if you put a for loop in main. Either way it just sounds like the OP is complaining rather than genuinely wondering why. – uh oh somebody needs a pupper Jun 02 '16 at 21:40
  • I'll vote to reopen after I gather 66 more reputation then. And @sleeptightpupper code [such as this](https://ideone.com/ZcdlHj) would cause problems if not in `constexpr`, it's why it can be expected that OP's code *could* work in `constexpr`. – coyotte508 Jun 02 '16 at 21:55
  • @coyotte508 For one thing, you have a `cout` statement in there, which makes it not `constexpr`. – uh oh somebody needs a pupper Jun 02 '16 at 21:57
  • @sleeptightpupper ok, removed that one, but not the one on line 11. But, ok. Although the link I first gave has a reference to [this proposal](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3597.html), partly implemented as `if` / `else` are now allowed in `constexpr` but mutable variables (with behavior that can be deduced at compile time) are still not. The case of the `for` loop in a constexpr is also mentioned there, and in my opinion answers the OP's question precisely. And it's not really an answer to the question which this is a duplicate of. – coyotte508 Jun 02 '16 at 22:13
  • πάνταῥεῖ, I also think it's not a dupe, for the reason @coyotte508 stated. And to be honest, I think it's a bit unfair to answer **and** dupe-hammer a question. Voting to reopen. – alain Jun 02 '16 at 22:16
  • @alain Well, I reopened the question. Come up with your better answers please. – πάντα ῥεῖ Jun 02 '16 at 22:18
  • ... pigheaded fellows, wasting appropriate close votes. – πάντα ῥεῖ Jun 02 '16 at 22:22
  • Thanks πάντα ῥεῖ! I do have some thought about it, but I'm not sure enough to post an answer. I would really love to see other answers to this question. (I'm not saying your answer is bad, and I didn't dv it). – alain Jun 02 '16 at 22:22
  • Well, I can offer you to vote to close another question on your behalf if you want... – alain Jun 02 '16 at 22:23
  • @alain Well, you tell me I reopened the question for such _wishy washy_ _"may be I have an answer, but I'm actually to shy to post it"_? – πάντα ῥεῖ Jun 02 '16 at 22:26
  • I have an answer, but I flagged the question as a duplicate (of the first link I gave), so I thought it was inappropriate to post it. Still, I commented on the OP. – coyotte508 Jun 02 '16 at 22:27
  • I voted to reopen because I think it's not a dupe, but an interesting question. That's all. – alain Jun 02 '16 at 22:32
  • @alain _"because I think it's not a dupe, but an interesting question"_ How does this contradict? There's not much expectation for getting more and better answers. – πάντα ῥεῖ Jun 02 '16 at 22:34
  • Ok, an *unanswered* interesting question. I'm interested in the reasons why certain constructs can not be used as `constexpr`, while the C++ template system is turing-complete. I think it has more to do with expressing a programmers intents, than with technical reasons. That's unanswered in the other question. – alain Jun 02 '16 at 22:41
  • @alain Well, again: `for()` never was meant to purpose for such stuff like unpacking templates.How should it, as it was introduced with the c language. – πάντα ῥεῖ Jun 02 '16 at 22:44
  • @πάνταῥεῖ for loop do can be used in compile time :constexpr int getsum(int n){ int s = 0; for(int i = 0; i < to; i++){ s += i; } return s; } ; get(tuple_variable); – camino Mar 23 '18 at 18:34
  • 15
    `if` also defines **runtime control flow**. But we still do have `if constexpr` since C++17. – Ruslan Mar 24 '18 at 08:14
0

Here are two examples attempting to replicate a compile-time for loop (which isn't part of the language at this time), using fold expressions and std::integer_sequence. The first example shows a simple assignment in the loop, and the second example shows tuple indexing and uses a lambda with template parameters available in C++20.

For a function with a template parameter, e.g.

template <int n>
constexpr int factorial() {
    if constexpr (n == 0) { return 1; }
    else { return n * factorial<n - 1>(); }
}

Where we want to loop over the template parameter, like this:

template <int N>
constexpr auto example() {
    std::array<int, N> vals{};
    for (int i = 0; i < N; ++i) {
        vals[i] = factorial<i>(); // this doesn't work
    }
    return vals;
}

One can do this:

template <int... Is>
constexpr auto get_array(std::integer_sequence<int, Is...> a) -> std::array<int, a.size()> {
    std::array<int, a.size()> vals{};
    ((vals[Is] = factorial<Is>()), ...);
    return vals;
}

And then get the result at compile time:

constexpr auto x = get_array(std::make_integer_sequence<int, 5>{});
// x = {1, 1, 2, 6, 24}

Similarly, for a tuple:

constexpr auto multiple_return_values()
{
    return std::make_tuple(3, 3.14, "pi");
}

int main(void) {
    static constexpr auto ret = multiple_return_values();
    constexpr auto for_constexpr = [&]<int... Is>(std::integer_sequence<int, Is...> a) {
        ((std::get<Is>(ret)), ...); // std::get<i>(t); from the question
        return 0;
    }

    // use it:
    constexpr auto w = for_constexpr(std::make_integer_sequence<int, std::tuple_size_v<decltype(ret)>>{});
}
Joe'
  • 376
  • 6
  • 10