Reputation: 14603
I am trying to initialize my custom vector object, but without using std::initializer_list
. I am doing something like this:
template <typename T, std::size_t N>
struct vector
{
template<std::size_t I = 0, typename ...Tp>
typename std::enable_if<I == sizeof...(Tp), void>::type
unpack_tuple(std::tuple<Tp...> const& t)
{
}
template<std::size_t I = 0, typename ...Tp>
typename std::enable_if<I != sizeof...(Tp), void>::type
unpack_tuple(std::tuple<Tp...> const& t)
{
store[I] = std::get<I>(t);
unpack_tuple<I + 1, Tp...>(t);
}
template<typename ...U>
vector(U&&... args,
typename std::enable_if<std::is_scalar<U...>::value, void>::type* = 0)
{
unpack_tuple(std::forward_as_tuple(std::forward<U>(args)...));
}
T store[N];
};
but the compiler does not grok the constructor unless I remove the std::enable_if
argument, which I need (as I don't want non-scalar arguments). Does there exist a solution?
Upvotes: 0
Views: 983
Reputation: 131789
std::is_scalar<U...>::value
The problem lies with the fact that is_scalar
only takes a single type argument. You need to write a wrapper that combines multiple boolean values. I also wonder why you use perfect forwarding if you only want scalar types anyways - just pass them by-value. This way, you also don't need to worry about U
being deduced as a reference when you get passed an lvalue.
#include <type_traits>
template<bool B>
using bool_ = std::integral_constant<bool, B>;
template<class Head, class... Tail>
struct all_of
: bool_<Head::value && all_of<Tail...>::value>{};
template<class Head>
struct all_of<Head> : bool_<Head::value>{};
template<class C, class T = void>
using EnableIf = typename std::enable_if<C::value, T>::type;
// constructor
template<typename... U>
vector(U... args, EnableIf<all_of<std::is_scalar<U>...>>::type* = 0)
{
unpack_tuple(std::tie(args...)); // tie makes a tuple of references
}
The above code should work. However, as an advice, if you don't want something, static_assert
that you don't get it and don't abuse SFINAE for that. :) SFINAE should only be used in overloaded contexts.
// constructor
template<typename... U>
vector(U... args)
{
static_assert(all_of<std::is_scalar<U>...>::value, "vector only accepts scalar types");
unpack_tuple(std::tie(args...)); // tie makes a tuple of references
}
So much for your actual question, but I recommend a better way to unpack tuples (or variadic arguments in general, or even an array), using the indices trick:
template<unsigned...> struct indices{};
template<unsigned N, unsigned... Is> struct indices_gen : indices_gen<N-1, N-1, Is...>{};
template<unsigned... Is> struct indices_gen<0, Is...> : indices<Is...>{};
template<unsigned... Is, class... U>
void unpack_args(indices<Is...>, U... args){
[](...){}((store[Is] = args, 0)...);
}
template<class... U>
vector(U... args){
static_assert(all_of<std::is_scalar<U>...>::value, "vector only accepts scalar types");
unpack_args(indices_gen<sizeof...(U)>(), args...);
}
What this code does is "abusing" the variadic unpacking mechanics. First, we generate a pack of indices [0 .. sizeof...(U)-1]
and expand then this list in lockstep together with args
. We put this expansion within a variadic (non-template) function argument list, as pack expansion can only occur at specific places, and this is one of them. Another possibility would be as a local array:
template<unsigned... Is, class... U>
void unpack_args(indices<Is...>, U... args){
int a[] = {(store[Is] = args, 0)...};
(void)a; // suppress unused variable warnings
}
Upvotes: 4