user9414424
user9414424

Reputation: 509

How can I use std::make_tuple at compile time?

Constexpr function that returns std::array<std:tuple<uint32_t, uint32_t, uint32_t>, size_t> does not work at compile time due to the use of std::make_tuple. Is there any way to overcome this?

When I tried to remove constexpr specifiction. It works correctly. However, the goal of our project is to provide such function evaluation at compile time.

I got the following error:

At calling part:

error: call to non-constexpr function ‘std::tuple<_Elements>& std::tuple<_Elements>::operator=(std::tuple<_Elements>&&) [with _Elements = {unsigned int, unsigned int, unsigned int}]’

At function part:

error: ‘constexpr std::array<std::tuple<unsigned int, unsigned int, unsigned int>, SIZE> GenArrayTuple() [with long unsigned int SIZE = 128]’ called in a constant expression

The code is below.

template<std::size_t SIZE>
constexpr std::array<std::tuple<uint32_t, uint32_t, uint32_t>, SIZE> 
GenArrayTuple() {
  std::array<std::tuple<uint32_t, uint32_t, uint32_t>, SIZE> array;
  for (uint32_t i = 0; i < SIZE; ++i) {
    // FIXME constexpr
    arr[2*i] = std::make_tuple(i, i * 2, i * 3 + 1);
  }
  return array;
}

constexpr uint32_t n = 128; 
constexpr auto array_tuple = GenArrayTuple<n>();

Upvotes: 2

Views: 2300

Answers (1)

aschepler
aschepler

Reputation: 72356

There's actually no issue with using std::make_tuple in a constant expression in C++14 or later, since C++14 changed it to be constexpr. So it's a valid constant expression, as long as any class constructors used to initialize the tuple's elements evaluate as valid constant expressions (and there are no such constructors when your element types are all scalars like std::uint32_t).

But take a better look at the error message. The function it complains about is (taking out some details) tuple& tuple::operator=(tuple&&). It turns out the assignment operators of std::tuple are not marked constexpr in current C++ versions, meaning any assignment of a tuple object is not a valid constant expression. (cppreference.com notes that they will be marked constexpr in C++20; this generally reflects changes from a proposal already accepted by the appropriate C++ working group.)

So to work around this, you'll need to initialize the array all at once, rather than assigning its elements in a loop. Probably the easiest way to do this is with the help of std::make_integer_sequence:

#include <tuple>
#include <array>
#include <cstdint>
#include <utility>

template <std::uint32_t ... I>
constexpr std::array<std::tuple<std::uint32_t, std::uint32_t, std::uint32_t>,
                     sizeof...(I)>
GenArrayTuple_helper(std::integer_sequence<std::uint32_t, I...>) {
    return { std::make_tuple(I, I * 2, I * 3 + 1) ... };
}

template <std::size_t SIZE>
constexpr std::array<std::tuple<std::uint32_t, std::uint32_t, std::uint32_t>,
                     SIZE> 
GenArrayTuple() {
    return GenArrayTuple_helper(std::make_integer_sequence<std::uint32_t, SIZE>{});
}

Upvotes: 2

Related Questions