15

I have the following class template:

template<class T, unsigned N>
class MyClass;

where T is some type, N - number of components. It is possible to initialize the class using MyClass{a1, a2, a3} where the number of arguments is equal to N.

I want to add a member function template (let's name it foo) of MyClass that would meet the following requirements:

  1. It is templated by another type T2 (i.e. template<class T2> void foo(..))
  2. It accepts enough data to construct MyClass<T,N>, but not less and not more. Violating this results in a compile-time error.
  3. It deduces T2 from the types of the parameters. I.e. I want that it would be possible to call foo({a1, a2, a3}) or foo(a1, a2, a3) or similar, without typing <double> or MyClass<double,N> every time.

Is there a way to implement the function so that the above requirements are satisfied?

I've already thought about and/or tried the following solutions:

1) The obvious one:

...
template<class T2>
void foo(MyClass<T2, N> arg);  
...
a.foo({1,2,3}); //compile-time error

Can't work in principle, because braced initializer lists are a non-deduced context, thus they can't deduce any types. That's quite unfortunate, I'd be very happy if this worked.

2) initializer_list

Can't work in principle, because it can't check the number of arguments at compile-time.

3) Variadic template magic

Something like the function below would be neat:

template<class...T2, class std::enable_if<sizeof...(T2) == N, int>::type = 0>
void foo(T2... args);
..
foo(1,2,3);

However, I couldn't get it to work - T2 still couldn't be deduced. Maybe someone knows why? I used GCC4.7 20120121 snapshot.

4) The ugly one

Essentially this is the same as the above one, just expanded into several overloads for different N. I would better reimplement MyClass as a set of specializations for different Ns than to use this one.

template<class T2, class std::enable_if<N == 1, int>::type = 0>
void fun(T2 a1); //if N == 1
template<class T2, ..>
void fun(T2 a1, T2 a2); //if N == 2
template<class T2, ..>
void fun(T2 a1, T2 a2, T2 a3); //if N == 3
...
p12
  • 1,161
  • 8
  • 23

4 Answers4

11

Your third variant's second non-type param should have prefix typename not class :

template<class...T2, typename std::enable_if<sizeof...(T2) == N, int>::type = 0>
void foo(T2... args);
..
foo(1,2,3);

Check it

Gcc 4.7.0 snapshots has some bugs with templates I guess, if you try it with gcc 4.6.2/1 it shall work.

Mr.Anubis
  • 5,132
  • 6
  • 29
  • 44
10

Why not use a static_assert?

template <typename T, size_t N>
class MyClass
{
public:
    template <typename... Args>
    void foo(Args&&... args)
    {
        static_assert(sizeof...(Args) == N, "Wrong number of arguments.");
        // Rest of the implementation.
    }
};
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 9
    There is one caveat with using static_assert this way: the overload resolution can still select this function even when it shouldn't. Using enable_if removes the overload altogether. Consider, for example, case when we also have nontemplated `foo(int,int,int)`. Then if we have `MyClass a`, calling `a.foo(0.5,0.5,0.5)` would select incorrect overload and fail. – p12 Feb 01 '12 at 20:24
1

Maybe like this:

#include <utility>
#include <type_traits>

template <typename T, unsigned int N>
class MyClass
{
    template <typename ...Args>
    typename std::enable_if<std::is_constructible<MyClass<T, N>, Args...>::value>::type
    foo(Args &&... args)
    {
        // for example:
        MyClass<T, N> m(std::forward<Args>(args)...);
        // ...
    }
};

This will work only if MyClass has a constructor that accepts the relevant arguments directly (like MyClass(A1, A2, A3)), but I don't think it works if MyClass has a constructor that requires an initializer list, nor will it work if MyClass is an aggregate that requires brace-initialization.

That said, it doesn't look like your MyClass could possibly accept an initializer list, since you said that it has to take precisely N arguments, which an IL cannot promise.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • One issue here is that in my case `T` and `T2/Args` are not necessarily related/convertible from each another. That is, the is_constructible check may fail for valid cases. What we really want to check is whether MyClass is constructible from Args, where T2 is some type to be deduced. – p12 Feb 01 '12 at 20:37
1

May be, based on your 3rd variant, you can extract first argument from initializer list and deduce type by it

template<class U, class...T2, class std::enable_if<sizeof...(T2) == N-1, int>::type = 0>
void foo(U u, T2... args)
{
    MyClass<U, N>({u, args...});
}
Lol4t0
  • 12,444
  • 4
  • 29
  • 65