Reputation: 428
In my unit tests, I want a quick and (cleanish) dirty way to assign values to a static-size C-array from an initializer_list
. I'm not a complete beast, so I want to static_assert
that the sizes are the same. I wrote a helper function set_array
to do this:
template <typename T, std::size_t N>
constexpr void set_array(T (&x)[N], std::initializer_list<T>&& list) {
assert(list.size() == N); // why can't I static_assert in C++17?
for (std::size_t i = 0; i < N; ++i)
x[i] = list.begin()[i];
}
with the intention of using it as set_array(foo, {1, 2, 3, 4});
with foo declared like int foo[4]
.
I'm using C++17, so std std::initializer_list<T>::size
is constexpr, but as soon as I pass the list through a function call, I lose the privilege of treating it as constexpr since I can't constrain the function parameters to be constexpr.
This feels like it should have a simple solution that I'm not seeing. Sure, I could imagine some perverse metaprogramming games I could play to encode the size in a type, but this is a simple little helper that's supposed to make things clean and readable, and I don't want to go nuts.
Question: Is there a simple solution, or should I just live with the runtime assert? (Yes, I know that if I'm given a simple solution, I'm going to feel stupid for not having seen it myself.) Think I'm going about it the wrong way? That's fine, I'm open to suggestions and appreciate the criticism.
Details: For completeness, here is the compiler error and version info. In Clang version 8.0.0-3 (That comes with Ubuntu clang-8) I get:
error: static_assert expression is not an
integral constant expression
static_assert(list.size() == N);
^~~~~~~~~~~~~~~~
And with GCC 8.3.0 I get a similar error, additionally telling me that list
is not a constant expression.
Upvotes: 2
Views: 474
Reputation: 180945
The reason this fails even though size
is constexpr
is because list
is not a constexpr variable so any member function calls on it will also not be constexpr
.
All is not lost though. What you can do is use a std::array
instead of a std::initializer_list
which lets you even get rid of the static_assert
like:
template <typename T, std::size_t N>
constexpr void set_array(T (&x)[N], std::array<T, N>&& list) {
for (std::size_t i = 0; i < N; ++i)
x[i] = list[i];
}
int main()
{
int arr[4];
set_array(arr, {1,2,3,4});
std::cout << arr[3];
}
If you tried using
set_array(arr, {1,2,3,4,5});
then you would get a compiler error like
main.cpp:12:16: note: candidate function [with T = int, N = 4] not viable: cannot convert initializer list argument to 'std::array<int, 4UL>'
constexpr void set_array(T (&x)[N], std::array<T, N>&& list) {
Upvotes: 2
Reputation: 275760
Arguments to functions are not constants.
This is legal in C++:
void foo( bool which ) {
std::initializer_list<int> a = {1,2,3};
std::initializer_list<int> b = {1,2,3,4};
int x[4];
std::initializer_list<int> c = which?a:b;
set_array(x, c);
}
try this:
template<class T>
struct block_deduction_t{using type=T;};
template<class T>
using block_deduction = typename block_deduction_t<T>::type;
template <typename T, std::size_t N>
constexpr void set_array(T (&x)[N], block_deduction<T const(&)[N]> list) {
for (std::size_t i = 0; i < N; ++i)
x[i] = list[i];
}
now this permits you to omit trailing elements, but they will be zero-initialized.
Then again, this solution:
template <typename T, std::size_t N>
constexpr void set_array(T (&x)[N], T const(&list)[N]) {
for (std::size_t i = 0; i < N; ++i)
x[i] = list[i];
}
actually blocks under-sized right hand sides. So might be better.
Syntax exactly matches yours.
If you want a pretty error message, and for the right hand types to be converted to match the left hand side types:
template <typename T, std::size_t N, std::size_t M>
constexpr void set_array(T (&x)[N], block_deduction<T> const(&list)[M]) {
static_assert(M==N, "wrong number of elements");
for (std::size_t i = 0; i < N; ++i)
x[i] = list[i];
}
there are many variants.
Upvotes: 2