3

I have a class which can be templated based on a single size parameter. I want to have a constructor which accepts a variable amount of std::array based on the template size parameter. So if the class is templated to one. It should accept a single array. If templated to two it should accept two etc.

This is what I came up with but obviously it doesn't work:

template<std::size_t V>
class Test
{
public:

    /* Constructors. */
    Test() {}

    template <std::array<int, 3> ...Args, typename = typename std::enable_if<V == sizeof...(Args), void>::type>
    Test(std::array<int, 3>&&... args) 
    {

    }

};


int main()
{
    auto t = Test<1>({ 1, 2, 3 });
    auto t2 = Test<2>(
        { 1, 2, 3 },
        { 4, 5, 6 }
    );
}

The error I recieve is:

error C2993: 'std::array<int,3>': illegal type for non-type template parameter 'Args'
note: see reference to class template instantiation 'Test<V>' being compiled
error C3543: 'std::array<int,3> &&': does not contain a parameter pack
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'Test<1>'
note: No constructor could take the source type, or constructor overload resolution was ambiguous
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'Test<2>'
note: No constructor could take the source type, or constructor overload resolution was ambiguous

Any help is appreciated.

  • 1
    How about moving `V == sizeof...(Args)` into `static_assert` inside of method? Actually it should be `template` and you will also need to check types of each argument. – user7860670 Sep 21 '17 at 21:29
  • @VTT I get almost the same error. I have posted it now in the question! –  Sep 21 '17 at 21:30
  • @VTT A `static_assert` inside a function generates an error later than proper template metaprogramming would, so can cause some problems with (e.g.) an invalid overload being chosen if there are multiple constructors. – Daniel H Sep 21 '17 at 21:40
  • @DanielH On the other hand `static_assert` may generate clearer error messages instead of some obscure "no matching function to call" with list of candidates. – user7860670 Sep 21 '17 at 21:58

2 Answers2

1

With inheritance, you may do:

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

template <typename T, typename Seq> struct TestHelper;

template <typename T, std::size_t ... Is>
struct TestHelper<T, std::index_sequence<Is...>>
{
    TestHelper(alwaysT<Is, T>... arr) {}
};

template<std::size_t V>
class Test : TestHelper<const std::array<int, 3>&, std::make_index_sequence<V>>
{
public:
    using TestHelper<const std::array<int, 3>&, std::make_index_sequence<V>>::TestHelper;
    Test() = default;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Could you generalize this to multiple dimensions and a custom type? Right now 3 is hardcoded and the type is int. Would be possible to parametize those also? I know this is beyond the scope of the question but I am curious. –  Sep 21 '17 at 23:53
  • Updated answer to allow some more customization. up to you to adapt to your needs. – Jarod42 Sep 22 '17 at 07:58
0

Some working solution from comment:

#include <type_traits>
#include <array>

template<typename... TArgs> struct
contains_only_array_of_3_int
{
    static constexpr bool const value{false};
};

template<typename TBack> struct
contains_only_array_of_3_int<TBack>
{
    static constexpr bool const value
    {
        ::std::is_same<::std::array<int, 3>, TBack>::value
    };
};

template<typename TFront, typename... TRest> struct
contains_only_array_of_3_int<TFront, TRest...>
{
    static constexpr bool const value
    {
        contains_only_array_of_3_int<TFront>::value
        &&
        contains_only_array_of_3_int<TRest...>::value
    };
};

template<std::size_t V> class
Test
{
    public:
    Test() {}

    template <typename... TArgs>
    Test(TArgs && ... args) 
    {
        static_assert
        (
            sizeof...(TArgs) == V
        ,   "arguments count mismatch"
        );
        static_assert
        (
            contains_only_array_of_3_int<::std::decay_t<TArgs>...>::value
        ,   "arguments type mismatch"
        );
    }
};

int main()
{
    Test<1> t
    {
        ::std::array<int, 3>{ 1, 2, 3 }
    };
    Test<2> t2
    {
        ::std::array<int, 3>{ 1, 2, 3 }
    ,   ::std::array<int, 3>{ 4, 5, 6 }
    };
    Test<3> t3
    {
        ::std::array<int, 3>{ 1, 2, 3 }
    ,   ::std::array<int, 3>{ 4, 5, 6 }
    ,   ::std::array<int, 3>{ 7, 8, 9 }
    };
    Test<3> t3_args_count_fail
    {
        ::std::array<int, 3>{ 1, 2, 3 }
    ,   ::std::array<int, 3>{ 4, 5, 6 }
    };
    Test<3> t3_args_tupe_fail
    {
        ::std::array<int, 3>{ 1, 2, 3 }
    ,   ::std::array<int, 2>{ 4, 5 }
    ,   ::std::array<int, 3>{ 7, 8, 9 }
    };
    return(0);
}

online editor

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Missing `std::decay_t` as you use forwarding reference. – Jarod42 Sep 21 '17 at 22:16
  • `contains_only_array_of_3_int` Can be simplified too, see [Demo](https://ideone.com/8ikS42) – Jarod42 Sep 21 '17 at 22:24
  • 1
    I’d use [`is_convertible`](http://en.cppreference.com/w/cpp/types/is_convertible) instead of `is_same`; otherwise, you lose the ability to call it with something which isn’t an `array` but can be implicitly converted to one. Otherwise this won’t behave quite the same as if you somehow hand-coded the function to take the right number of parameters. – Daniel H Sep 21 '17 at 22:40