Home > other >  How to initialize a constexpr struct containing arrays without using intermediate variables?
How to initialize a constexpr struct containing arrays without using intermediate variables?

Time:10-26

Is it possible to initialize a constexpr struct which contains array-like fields without defining intermediate variables. Using intermediates is the only solution I could find to workaround compilation errors due to passing temporary arrays, but that pattern seems more verbose than necessary.

I'm open to using something other than std::span for the field member types to enable a cleaner pattern.

#include <span>

struct MyStruct {
    // Feel free to replace span with array, etc.
    std::span<const int> a;
    std::span<const int> b;
    // ... contains many array-like fields
};

// Would like to avoid creating these intermediate variables
constexpr std::array my_a{1,2,3};
constexpr std::array my_b{5,6};
// ...

constexpr MyStruct foo{
    .a = my_a, // Works, but requires annoying intermediate
    // .a = {1,2,3}, // Error, no matching std::span constructor
    // .a = {{1,2,3}}, // Error, creates non-constexpr temporary
    // .a = std::array{1,2,3}, // Error, creates non-constexpr temporary
    .b = my_b,
    // ...
};

constexpr MyStruct bar{
    // Will create different instances with a variety of field sizes
    //.a = {{1,2}},
    //.b = {{1,2,3,4,5}},
    // ...
};

CodePudding user response:

Put your value in a template parameter:

constexpr MyStruct foo{
  .a = std::integral_constant<std::array<int, 3>, std::array{1, 2, 3}>::value
};

// or more tersely
template<auto V>
inline constexpr auto static_storage(V);

constexpr MyStruct foo{
  .a = static_storage<std::array{1, 2, 3}>
};

And you can use a lambda for non-structural types:

template<auto F>
inline constexpr auto call_static(F());

constexpr MyStruct foo{
    .a = call_static<[]{ return std::array{1, 2, 3}; }>
    .b = call_static<[]{ return std::array{5, 6}; }>
};


// In a macro if you want
#define STATIC_STORAGE(...) (call_static<[]() -> decltype(auto) { return (__VA_ARGS__) ; }>)

Since you asked in the comments, in C 17 it is quite a bit more difficult to use arrays in template arguments.

For simple types, you can use a parameter pack:

template<auto First, decltype(First)... Rest>
inline constexpr std::array<decltype(First), sizeof...(Rest) 1u> static_array{{First, Rest...}};

// .a = static_array<1, 2, 3>

For more complicated types, you can still use a lambda but pass it in more creatively:

template<typename Lambda>
struct call_static_holder {
    static_assert(std::is_empty_v<Lambda> && std::is_trivially_copy_constructible_v<Lambda>, "Lambda must be stateless");
    static constexpr Lambda lambda() {
        // No default constructor in C  17, copy all 0 members instead
        const Lambda l(l);
        return l;
    }
    static constexpr decltype(auto) value = lambda()();
};

template<typename Lambda>
constexpr auto& call_static(Lambda) {
    return call_static_holder<Lambda>::value;
}

// .a = call_static([]{ return std::array{1, 2, 3}; })
  •  Tags:  
  • c
  • Related