22

Is it possible to constrain the type of arguments in a variadic constructor?

I want to be able to express

X x1(1,3,4);
X x2(3,4,5);

// syntax error: identifier 'Args'
class X {
template<int ... Args> X(Args...)
{
}
};
// this works but allows other types than int
class Y {
template<typename ... Args> Y(Args...)
{
}
};

edit to clarify intent:

What I want to achieve is to store data passed into a constructor (constants known at compile time) into a static array.

so there are some other

template<int ...values>
struct Z
{
    static int data[sizeof...(values)];
};

template<int ... values>
int Z<values...>::data[sizeof...(values)] = {values...};

and in the constructor of X I would like to use Z like this:

class X {
    template<int ... Args> X(Args...)
    {
        Z<Args...>::data // do stuff with data
    }
};

Is that possible, our do I have to use integer_sequence?

4 Answers4

22

You can use std::initializer_list:

#include <iostream>
#include <initializer_list>

void myFunc(std::initializer_list<int> args)
{
    for (int i: args) std::cout << i << '\n';
}
int main(){

    myFunc({2,3,2});
    // myFunc({2,"aaa",2}); error!

}
granmirupa
  • 2,780
  • 16
  • 27
17

Since you have the following:

template<int... values>
struct Z
{
    static int data[ sizeof...( values ) ];
};

template <int... values>
int Z<values...>::data[ sizeof...( values ) ] = { values... };

You can use std::integer_sequence<> to pass in the ints to Z<>:

struct X
{
    template <int... values>
    X( std::integer_sequence<int, values...> )
    {
        for ( int i{ 0 }; i < sizeof...( values ); ++i )
            Z<values...>::data[ i ]; // do stuff with data
    }
};

You can make yourself a helper type to make it easy to call the ctor:

template <int... values>
using int_sequence = std::integer_sequence<int, values...>;

Then you can instantiate your class like so:

int main()
{
    X x( int_sequence<1, 3, 5>{} );
}
user2296177
  • 2,807
  • 1
  • 15
  • 26
1

You've updated your question to indicate that all you need is a compile-time std::integer_sequence, which is great.

But just for the sake of future readers who might come here looking for the answer, I'd like to also answer your original question "Is it possible to constrain the type of arguments in a variadic constructor?"

Yes. One way (the best way? I'm not sure) is to SFINAE on an extra template parameter, like this:

struct X {
    template<
        class... TT,
        class E = std::enable_if_t<(std::is_same_v<TT, int> && ...)>
    >
    X(TT... tt) {
        // do stuff with the ints "tt..."
    }
};

The && ... is a fold-expression, new in C++17. If your compiler doesn't support fold-expressions, just replace that with a hand-rolled all_of.

Community
  • 1
  • 1
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
-6

No, you can't constrain the type. You can use static_assert though. Would be something like this:

static_assert(std::is_same<int, Args>::value ..., "have to be ints.");

Have not tried to use an expansion in a static_assert like that though. You may need a constexpr that returns bool or something.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • Yes, you can constrain the type. And no, you can't have pack expansion in a `static_assert` like that. – Barry May 20 '16 at 17:00