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}; })