0

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? Commented Jan 31, 2019 at 10:32
  • Do you want to initialize arr with any value convertible to T type or just values of type T? Commented Jan 31, 2019 at 10:35
  • [at]max66, yes that's a typo in the excerpt, sorry. @DanM. I would like to construct HasArray with any value convertible. Commented Jan 31, 2019 at 10:44

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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

2 Comments

Hm, I'm not sure if I like the actual type having the full index sequence as template parameters. It's valid C++ of course, but you will make a mess of a lot of tools, error messages, binary size (think about the mangled name of void foo(HasArray<int, 100>& x))... This answer is technically correct but imo not advisable in practice.
Also note that std::is_convertible is more strict than static_cast. The former only considers implicit conversions, whereas the latter can do a good deal more.
1

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

2 Comments

You probably want a static_assert(N == sizeof...(Is)), else HasArray<int, 42, std::index_sequence<>> would be valid type :-/
@Jarod42 - I seemed to forget something: HasArray can be hijacked. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.