Initializing static constexpr member of type std::array in C++14
Asked Answered
A

1

0

I'm failing to programmatically initialize a static constexpr std::array member.
This is a minimal example of my issue (for simplification size is known and small, thus initialization could be manual but I'd like to make the actual size a template non-type parameter, thus ruling out a manual initialization):

#include <array>

constexpr std::size_t N = 3;
using Mat = std::array<double, N * N>;

// OK, can initialize a free constexpr Mat
constexpr Mat InitEye() noexcept {
    Mat TmpEye{0};
    for (std::size_t r = 0; r < N; ++r) {
        for (std::size_t c = 0; c < N; ++c) {
            TmpEye[r * N + c] = (r == c) ? 1. : 0.;
        }
    }
    return TmpEye;
}

// KO
class Wrapper {
   private:
    // KO cannot use it to initialize static constexpr member
    static constexpr Mat WrappedInitEye() noexcept {
        Mat TmpEye{0};
        for (std::size_t r = 0; r < N; ++r) {
            for (std::size_t c = 0; c < N; ++c) {
                TmpEye[r * N + c] = (r == c) ? 1. : 0.;
            }
        }
        return TmpEye;
    }

   public:
    static constexpr Mat Eye = WrappedInitEye();
};

// also KO
class Wrapper2 {
   public:
    // OK in C++17, still KO in C++17 due to lack of constexpr access operator
    static constexpr Mat Eye = [] {
        Mat TmpEye{0};
        for (std::size_t r = 0; r < N; ++r) {
            for (std::size_t c = 0; c < N; ++c) {
                TmpEye[r * N + c] = (r == c) ? 1. : 0.;
            }
        }
        return TmpEye;
    }();
};

int main() {
    constexpr Mat Eye = InitEye();
    constexpr Mat Eye2 = Wrapper::Eye;
    return 0;
}

The closest answer I found is this one (thus the lambda version above).

Yet live example si showing two issues:

  1. Non lambda version never works:
\<source\>:32:46: error: 'static constexpr Mat Wrapper::WrappedInitEye()' called in a constant expression before its definition is complete
   32 |     static constexpr Mat Eye = WrappedInitEye();
  1. Lambda version does not work either in C++14, due to the lack of constexpr access function for std::array

The non-lambda version gives this "uncomplete definition" error because:

But still, how can I implement a programmatic std::array initialization with C++14?

Affer answered 7/7, 2023 at 16:39 Comment(2)
static constexpr function called in a constant expression is...an error?Publican
I saw it and first thought it was unrelated but the answer actually explain point 1. Thanks.Affer
B
3
  1. Why does the non-lambda version gives this "uncomplete definition" error?

Because the definition of the static member function WrappedInitEye is not complete until the end of the class definition. I'm not quite sure where in the standard this is addressed, but that particular error disappears if you make WrappedInitEye a free function instead.

  1. How can I implement a programmatic std::array initialization with C++14?

Best I could do in C++14 is this:

#include <array>
#include <utility>

constexpr std::size_t N = 3;
using Mat = std::array<double, N * N>;

template<std::size_t Index>
static constexpr bool condition = Index / N == Index % N;

template<std::size_t Index>
constexpr auto genElement(char(*)[condition<Index>] = 0) -> double {
    return 1.;
}

template<std::size_t Index>
constexpr auto genElement(char(*)[!condition<Index>] = 0) -> double {
    return 0.;
}

template<std::size_t... Indices>
constexpr auto gen(std::index_sequence<Indices...>) {
    return Mat{ genElement<Indices>()... };
}

int main() {
    constexpr Mat a = gen(std::make_index_sequence<N*N>());
}

Demo

It only works for your particular pattern (identity matrix, so a 1 when Index / N == Index % N, a 0 otherwise), but you might be able to modify it for more complex patterns. The char(*)[condition<Index>()] = 0 part uses SFINAE. I got it from here.

You can also use tag dispatching:

template<std::size_t Index>
using Condition = std::integral_constant<bool, Index / N == Index % N>;

template<std::size_t Index>
constexpr auto genElement(std::true_type) -> double {
    return 1.;
}

template<std::size_t Index>
constexpr auto genElement(std::false_type) -> double {
    return 0.;
}

template<std::size_t... Indices>
constexpr auto gen(std::index_sequence<Indices...>) {
    return Mat{ genElement<Indices>(Condition<Indices>{})... };
}

Demo

Balboa answered 7/7, 2023 at 17:22 Comment(2)
I think that your idea of using a index_sequence with some partial specialization or SFINAE can be applied to more general situation. Thank you.Affer
@Affer I just noticed condition could just be a variable. Also I added a tag dispatching version, in case that's clearer.Balboa

© 2022 - 2024 — McMap. All rights reserved.