I wrote code like this
#include <iostream>
using namespace std;
constexpr int getsum(int to){
int s = 0;
for(int i = 0; i < to; i++){
s += i;
}
return s;
}
int main() {
constexpr int s = getsum(10);
cout << s << endl;
return 0;
}
I understand that it works because of extended constexpr. However in this question why-isnt-a-for-loop-a-compile-time-expression, the author gave his code as follow:
#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);
}
It could not compile by GCC5.1, however, after replacing std::get<i>(t);
with something without a specified template parameter his code did work.
After reading answers under this question I found their main point is constexpr for
creates trouble for the compiler so for-loop is used at run-time.
So it makes me confused, on one hand, for-loop
is at run-time, on the other, the loop is in a constexpr
function, so it must be calculated at compile time, so there seems to be a contradiction. I just wonder where did I make a mistake.
Your function need to be valid both at runtime and at compile time. So your Variant i
can be seen as a runtime variable. If the function is executed at compile time, the variable i
is part of the compiler's runtime. So a int
in a constexpr function is under the same rules as a int
in a non-constexpr function.
What you can do is to create your own constexpr for loop:
template<typename F, std::size_t... S>
constexpr void static_for(F&& function, std::index_sequence<S...>) {
int unpack[] = {0,
void(function(std::integral_constant<std::size_t, S>{})), 0)...
};
(void) unpack;
}
template<std::size_t iterations, typename F>
constexpr void static_for(F&& function) {
static_for(std::forward<F>(function), std::make_index_sequence<iterations>());
}
Then, you can use your static_for
like this:
static_for<std::tuple_size<T>::value>([&](auto index) {
std::get<index>(t);
});
Note that lambda function cannot be used in constexpr function until C++17, so you can instead roll your own functor:
template<typename T>
struct Iteration {
T& tuple;
constexpr Iteration(T& t) : tuple{t} {}
template<typename I>
constexpr void operator() (I index) const {
std::get<index>(tuple);
}
};
And now you can use static_for
like that:
static_for<std::tuple_size<T>::value>(Iteration{t});
This really has nothing to do with whether or not the constexpr
function needs to be able to be runnable at compile-time (tuple_size<T>::value
is a constant expression regardless of whether the T
comes from a constexpr
object or not).
std::get<>
is a function template, that requires an integral constant expression to be called. In this loop:
for (auto i = 0u; i < std::tuple_size<T>::value; ++i)
{
std::get<i>(t);
}
i
is not an integral constant expression. It's not even constant. i
changes throughout the loop and assumes every value from 0
to tuple_size<T>::value
. While it kind of looks like it, this isn't calling a function with different values of i
- this is calling different functions every time. There is no support in the language at the moment for this sort of iteration†, and this is substantively different from the original example, where we're just summing ints.
That is, in one case, we're looping over i
and invoking f(i)
, and in other, we're looping over i
and invoking f<i>()
. The second one has more preconditions on it than the first one.
†If such language support is ever added, probably in the form of a constexpr for
statement, that support would be independent from constexpr
functions anyway.
Some better approach as Guillaume Racicot have mentioned with workaround of a bit unfinished constexpr
support and std::size
in such compilers like Visual Studio 2015 Update 3
and so.
#include <tuple>
#include <stdio.h>
#include <assert.h>
// std::size is supported from C++17
template <typename T, size_t N>
constexpr size_t static_size(const T (&)[N]) noexcept
{
return N;
}
template <typename ...T>
constexpr size_t static_size(const std::tuple<T...> &)
{
return std::tuple_size<std::tuple<T...> >::value;
}
template<typename Functor>
void runtime_for_lt(Functor && function, size_t from, size_t to)
{
if (from < to) {
function(from);
runtime_for_lt(std::forward<Functor>(function), from + 1, to);
}
}
template <template <typename T_> class Functor, typename T>
void runtime_foreach(T & container)
{
runtime_for_lt(Functor<T>{ container }, 0, static_size(container));
}
template <typename Functor, typename T>
void runtime_foreach(T & container, Functor && functor)
{
runtime_for_lt(functor, 0, static_size(container));
}
template <typename T>
void static_consume(std::initializer_list<T>) {}
template<typename Functor, std::size_t... S>
constexpr void static_foreach_seq(Functor && function, std::index_sequence<S...>) {
return static_consume({ (function(std::integral_constant<std::size_t, S>{}), 0)... });
}
template<std::size_t Size, typename Functor>
constexpr void static_foreach(Functor && functor) {
return static_foreach_seq(std::forward<Functor>(functor), std::make_index_sequence<Size>());
}
Usage:
using mytuple = std::tuple<char, int, long>;
template <typename T>
struct MyTupleIterator
{
T & ref;
MyTupleIterator(T & r) : ref(r) {}
void operator() (size_t index) const
{
// still have to do with switch
assert(index < static_size(ref));
size_t value;
switch(index) {
case 0: value = std::get<0>(ref); break;
case 1: value = std::get<1>(ref); break;
case 2: value = std::get<2>(ref); break;
}
printf("%u: %u\n", unsigned(index), unsigned(value));
}
};
template <typename T>
struct MyConstexprTupleIterator
{
T & ref;
constexpr MyConstexprTupleIterator(T & r) : ref(r) {}
constexpr void operator() (size_t index) const
{
// lambda workaround for:
// * msvc2015u3: `error C3250: 'value': declaration is not allowed in 'constexpr' function body`
// * gcc 5.x: `error: uninitialized variable ‘value’ in ‘constexpr’ function`
[&]() {
// still have to do with switch
assert(index < static_size(ref));
size_t value;
switch(index) {
case 0: value = std::get<0>(ref); break;
case 1: value = std::get<1>(ref); break;
case 2: value = std::get<2>(ref); break;
}
printf("%u: %u\n", unsigned(index), unsigned(value));
}();
}
};
int main()
{
mytuple t = std::make_tuple(10, 20, 30);
runtime_foreach<MyTupleIterator>(t);
mytuple t2 = std::make_tuple(40, 50, 60);
runtime_foreach(t2, [&](size_t index) {
// still have to do with switch
assert(index < static_size(t2));
size_t value;
switch(index) {
case 0: value = std::get<0>(t2); break;
case 1: value = std::get<1>(t2); break;
case 2: value = std::get<2>(t2); break;
}
printf("%u: %u\n", unsigned(index), unsigned(value));
});
mytuple t3 = std::make_tuple(70, 80, 90);
static_foreach<std::tuple_size<decltype(t3)>::value>(MyConstexprTupleIterator<mytuple>{t3});
return 0;
}
Output:
0: 10
1: 20
2: 30
0: 40
1: 50
2: 60
0: 70
1: 80
2: 90
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With