简体   繁体   中英

`static_assert` on `std::initializer_list<T>::size` in a function

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.

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.

Live example .

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.

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) {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM