0
votes

There's structure HasArray with template parameters typename T and size_t N.

template<typename T, size_t N>
struct HasArray {
  // enable_if sizeof...(T) equals N
  template<typename ... Types, typename std::enable_if<sizeof...(Types) == N, int>::type = 0>
  explicit HasArray(Types ... s) : arr { s... } {}
 protected:
  std::array<T, N> arr;
};

I would like to initialize the member array arr with the parameter-pack arguments of the constructor.

HasArray<uint32_t, 2> foo(7, 13);

But that yields a c++11-narrowing warning in Clang.

error: non-constant-expression cannot be narrowed from type 'int' to 'std::__1::array<unsigned int, 2>::value_type' (aka 'unsigned int') in initializer list [-Wc++11-narrowing]

I see no way to cast all s's of type Types to type T. Is there one?

Edit thanks for all answers. I ended up using static_cast<> on the packed parameters and SFINAE when not convertible:

template<typename T, size_t N>
struct HasArray {
  // Use `static_cast` to construct `arr` with `s`
  // Add `enable_if` all `Types` are `is_convertible`
  template<typename ... Types, 
    typename std::enable_if<sizeof...(Types) == N, int>::type = 0,
    typename std::enable_if<(std::is_convertible<Types, T>::value && ...), int>::type = 0>
  explicit HasArray(Types ... s) : arr { static_cast<T>(s)... } {}
 protected:
  std::array<T, N> arr;
};
3
I suppose is sizeof...(Types) == N; anyway: C++11, C++14 or C++17?max66
Do you want to initialize arr with any value convertible to T type or just values of type T?Dan M.
[at]max66, yes that's a typo in the excerpt, sorry. @DanM. I would like to construct HasArray with any value convertible.Tom

3 Answers

4
votes

If you want to construct std::array from any value convertible to T, then the easiest solution would be just to add static_cast<T>(...) in your constructor.

template<typename T, size_t N>
struct HasArray {
  template<typename ... Types,
           typename std::enable_if<sizeof...(Types) == N, int>::type = 0>
  explicit HasArray(Types ... s) : arr {static_cast<T>(s)... } {}
protected:
  std::array<T, N> arr;
};

https://godbolt.org/z/TEoZOG

It's also possible to "SFINAE out" the constructor in case such conversion is not possible, but In my opinion the default error message would be better in current simple case and you could add static_asserts with better message in constructor body.

2
votes

You might use intermediate class:

template <std::size_t, typename T>
using always_t = T;

template <typename T, typename Seq>
struct HasArrayImpl;

template <typename T, std::size_t...Is>
struct HasArrayImpl<T, std::index_sequence<Is...>>
{
    explicit HasArrayImpl(always_t<Is, T> ... v) : arr { v... } {}
protected:
    std::array<T, sizeof...(Is)> arr;
};

template <typename T, std::size_t N>
using HasArray = HasArrayImpl<T, std::make_index_sequence<N>>;

Else, you can extend your SFINAE to convertible type and explicitly convert values

template<typename T, size_t N>
struct HasArray {
    // enable_if sizeof...(T) equals N
    template <typename ... Types,
             std::enable_if_t<(sizeof...(Types) == N)
                                   && (std::is_convertible<Types, T>::value && ...),
                              int>::type = 0>
    explicit HasArray(Types ... s) : arr{ static_cast<T>(s)... } {}
 protected:
    std::array<T, N> arr;
};
1
votes

I see no way to enforce that all types Types must be of type T. Is there one?

I don't understand, from your question if you want that Types are deducible template types, and all of they are deduced exactly as T, or if you want a no-template constructor that receive exaclty N values of type T.

The first case is simple (if you can use C++17 template folding; a little more complicated otherwise) because you can use std::is_same

template <typename ... Types,
          typename std::enable_if<(sizeof...(Types) == N) 
              && (... && std::is_same<Types, T>::value), int>::type = 0>
explicit HasArray(Types ... s) : arr {{ s... }}
 { }

For second case I propose a variation of the Jarod42 solution that uses a specialization of HasArray instead of a intermediate class (edit: added an improvement from Jarod42 itself; thanks!):

template<typename T, std::size_t N, typename = std::make_index_sequence<N>>
struct HasArray;

template<typename T, std::size_t N, std::size_t ... Is>
struct HasArray<T, N, std::index_sequence<Is...>>
 {
   static_assert( sizeof...(Is) == N , "wrong sequence size" );

   protected:
      std::array<T, N> arr;

   public:
      explicit HasArray(getType<T, Is> ... s) : arr {{ s... }}
       { }
 };

where getType is

template <typename T, std::size_t>
using getType = T;

In first case,

HasArray<std::uint32_t, 2> foo(7, 13);

gives compilation error because 7 and 13 are deduced as int that isn't std::uin32_t.

In second case it compile becase HasArray has a constructor

HasArray(std::uint32_t, std::uint32_t)

and the ints are converted to std::uint32_t.

The following is a full compiling C++14 example for the second case

#include <array>
#include <cstdint>
#include <type_traits>

template <typename T, std::size_t>
using getType = T;

template<typename T, std::size_t N, typename = std::make_index_sequence<N>>
struct HasArray;

template<typename T, std::size_t N, std::size_t ... Is>
struct HasArray<T, N, std::index_sequence<Is...>>
 {
   static_assert( sizeof...(Is) == N , "wrong sequence size" );

   protected:
      std::array<T, N> arr;

   public:
      explicit HasArray(getType<T, Is> ... s) : arr {{ s... }}
       { }
 };

int main ()
 {
   HasArray<std::uint32_t, 2> foo(7, 13);
 }