Question

In his answer to my question Avoiding struct in variadic template function iavr remarked that "std::array::operator[] is constexpr only in C++14". My question here is to make sure that GCC behavior is inconsistent and that's not me wrongly understanding the standard.

I'm exploring different way of using some template meta programming to initialize a two dimensional array to the Pascal triangle (with 0 outside). In the one I'm trying here, I want to avoid as much as possible to use template structure and in particular variadic in favor if constexpr function and arrays.

Note for reader in a hurry: I'm putting the three following pieces of code for sake of completeness but you dont need to understand them.


I'm using the two following pretty standard definitions:

template <typename... Ts> struct Sequence {};
template<unsigned N, unsigned... Is> struct Range {
    typedef typename Range<N-1, N-1, Is...>::type type;
};
template<unsigned... Is> struct Range<0, Is...> {
    typedef Sequence<std::integral_constant<unsigned int, Is>...> type;
};

Then I have the following template constexpr function which given a line of the triangle, compute the next one:

// nextline
template <typename... SeqTis, typename T, size_t Size>
constexpr std::array<T, Size>
nextline(Sequence<SeqTis...>, const typename std::array<T, Size> ar) {
  return { 1, (ar[SeqTis::value]+ar[SeqTis::value+1])... };
}
template <typename T, size_t Size>
constexpr std::array<T, Size>
nextline(const typename std::array<T, Size> ar) {
  return nextline(typename Range<Size-1>::type(), ar);
}

And the following add an element at the end of a partially initialized array:

template <typename... SeqTis, typename T, size_t Size>
constexpr std::array<T, Size>
appendarray(Sequence<SeqTis...>, const typename std::array<T, Size> ar, const T el) {
  return { ar[SeqTis::value]..., el };
}
template <size_t Pos, typename T, size_t Size>
constexpr std::array<T, Size>
appendarray(const typename std::array<T, Size> ar, const T el) {
  return appendarray(typename Range<Pos>::type(), ar, el);
}

In these code I'm using array indexing and it perfectly works. As you can try with:

constexpr auto ar0 = std::array<int, 3> { 1,0,0 };
constexpr auto ar1 = nextline(ar0);
constexpr auto ar2 = appendarray<2>(ar1, 12);
for (auto i: ar2) std::cout << i << " "; // prints 1 1 12

But when I try to compile the following recursive struct:

template <typename T, size_t N>
using Ar2  = std::array<std::array<T, N+1>, N+1>;

template<typename T, size_t N, size_t l> struct Loop {
  constexpr static Ar2<T, N> next() {
    return appendarray<l>(Loop<T, N, l-1>::next(),
                  nextline(Loop<T, N, l-1>::next()[l-1]));
  }
};

template<typename T, size_t N> struct Loop<T, N, 0> {
  constexpr static Ar2<T, N> next() {
    return Ar2<T, N>({ {1, 0} });
  }
};

};

Then GCC complains with

[...]
binom2.cpp:48:30: note: ‘static constexpr Ar2<T, N> Loop<T, N, l>::next() [with T = long long int; long unsigned int N = 10ul; long unsigned int l = 10ul; Ar2<T, N> = std::array<std::array<long long int, 11ul>, 11ul>]’ is not usable as a constexpr function because:

constexpr static Ar2<T, N> next() {
                              ^
binom2.cpp:50:38: error: call to non-constexpr function ‘std::array<_Tp, _Nm>::value_type& std::array<_Tp, _Nm>::operator[](std::array<_Tp, _Nm>::size_type) [with _Tp = std::array<long long int, 11ul>; long unsigned int _Nm = 11ul; std::array<_Tp, _Nm>::reference = std::array<long long int, 11ul>&; std::array<_Tp, _Nm>::value_type = std::array<long long int, 11ul>; std::array<_Tp, _Nm>::size_type = long unsigned int]’
  nextline(Loop<T, N, l-1>::next()[l-1]));

It seems that sometimes GCC allows constexpr array indexing, and sometimes it doesn't. Am I missing something.

Was it helpful?

Solution

From what I understand,

T& std::array<T,N>::operator[]

is not constexpr, but

 constexpr const T& std::array<T,N>::operator[] const

Is...you have to manually cast anything that returns an (non-const) std::array with (const std::array&) to get it to select the correect operator.

After some finagling with the cast syntax, it looks like the correct syntax is:

nextline(((const Ar2<T, N>&) Loop<T, N, l-1>::next())[l-1])

though you may still want to look into const_cast as the regular cast will remove the rvalue-ness of the function call.

OTHER TIPS

The simplest fix is to declare the next function to return a constexpr const array:

template<typename T, size_t N, size_t l> struct Loop {
  constexpr const static Ar2<T, N> next() {
    return appendarray<l>(Loop<T, N, l-1>::next(),
                  nextline(Loop<T, N, l-1>::next()[l-1]) );
  }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top