I'm writing a container storage class template that wraps a private std::array in order to add some functionality to it. The template parametrises the number of values, as follows:
template<size_t N> class Vector {
private:
array<double, N> vals;
public:
[...]
};
I'd like the constructor for the class to only accept N doubles to fill the array, but I can't find a good way to do this. Variadic arguments don't provide a mechanism to check how many of them there are, so they're right out. Parameter packs don't do floating-point promotion, but I'd be willing to deal with that if I could only figure out how to use them for this.
I've tried following the approach in the answer to Member function template with the number of parameters depending on an integral template parameter but I can't understand the significance enable_if<>::type=0 section. I've tried naïvely copying that code in (though I'd much rather understand how it works. I've seen people use ::value in other places but I can't find any documentation on why) but expanding the resulting parameter pack doesn't seem to work. My other concern with parameter packs is that I'm not sure that they'd ensure the types of all arguments were the same.
I've tried running a static_assert on the size of an initializer list, in the body of the constructor, but of course the size of the list is not constant at compile time, so that doesn't work either.
Is there a standard approach here? Am I just using parameter packs wrong?
Update:
I've got the approach in the answer I linked above partly working:
template<size_t N> class Vector {
private:
array<double, N> vals;
public:
template <typename ...T,
typename enable_if<sizeof...(T) == N, int>::type = 0>
Vector(T ...args) {
vals = {args...};
}
};
The issue is now that the enable_if term in the template means that when I initialise a Vector with, for example,
Vector<3> V {1.0, 2.0, 3.0};
It requests a template specialisation Vector<3>::Vector<double, double, double, 0> rather than <double, double, double>. How do I get rid of this stray term in the template?
Don't get what you mean by this:
Variadic arguments don't provide a mechanism to check how many of
them there are, so they're right out
template <typename ...T>
Vector(T... args) {
static_assert(sizeof...(args) <= N, "oops");
}
Should work..
You could additionally generate a pack of the right size via some template specialization tricks:
template <size_t N, class = std::make_index_sequence<N>>
class Vector;
template <size_t N, size_t... Is>
class Vector<N, std::index_sequence<Is...>>
{
private:
std::array<double, N> vals;
template <size_t >
using double_ = double;
public:
Vector(double_<Is>... vals)
{
...
}
};
That is a non-template constructor which takes N doubles.
By following Member function template with the number of parameters depending on an integral template parameter and Variadic templates with exactly n parameters, have got it working with the following code.
template<size_t N> class Vector {
private:
array<double, N> vals;
public:
template <typename ...T,
typename enable_if<sizeof...(T) == N, int>::type = 0>
Vector(T ...args) : vals{args} {}
};
Related
I'm looking for a way to recursively replace a type in a template. More specifically, turn T[N] into std::array<T, N>. The problem is doing this recursively if non-type template parameters are included.
Currently, I have the following code:
template<typename T>
struct replaced {
using type = T;
};
template<typename T, std::size_t N>
struct replaced<T[N]> {
using type = std::array<typename replaced<T>::type, N>;
};
template<template<typename...> typename T, typename... Ts>
struct replaced<T<Ts...>> {
using type = T<typename replaced<Ts>::type...>;
};
Test cases without non-type template parameters work fine, but as soon as a non-type template parameter is introduced, it no longer works:
// this works (simple replacement)
static_assert(std::is_same_v<replaced<int[3]>::type, std::array<int, 3>>);
// this also works (nested types)
static_assert(std::is_same_v<
replaced<std::tuple<int[2], std::pair<int, float[3]>>>::type,
std::tuple<std::array<int, 2>, std::pair<int, std::array<float, 3>>>>);
// this doesn't work (non-type template parameters)
static_assert(std::is_same_v<
replaced<std::array<int[2], 2>>::type,
std::array<std::array<int, 2>, 2>>);
// instead this works, but shouldn't
static_assert(std::is_same_v<
replaced<std::array<int[2], 2>>::type,
std::array<int[2], 2>>);
Link to compiler explorer
I understand that the variadic template template parameter doesn't work for non-type templates parameters. Is there any way of modifying the definitions of replaced to achieve my goal?
First off, cool idea!
But as much as it sucks, as of C++20 there's no way to handle a generic, variadic mix of type and non-type elements. You just have to start carving out corner cases like with std::array.
This seems to fix that specific problem, but of course it only helps for classes that follow the same pattern as std::array
template <template<typename, auto> class TC, typename T, auto N>
struct replaced<TC<T, N>> {
using type = TC<typename replaced<T>::type, N>;
};
https://godbolt.org/z/3j13W3
I want to write a function taking N arguments of type T. The use case is to initialize an array in a class with an initializer list.
Vector<uint8_t, 3> vector {5, 4, 3}
Vector<uint16_t, 2> vector2 {90, 7}
The reason, I don't use std::initalizer list is that for the platform I develop, no standard library is available. Therefore I tried some approaches with a self-made integer_list (similar to the one from std).
template <uint8_t ... Ints >
class IndexSequence {};
This works perfectly fine (Array is also similiar to std::array):
template <typename T, uint8_t I>
using ParamPack = T;
template <typename T, uint8_t N, typename I = makeIndexSequence<N>>
class Test;
template <typename T, uint8_t N, uint8_t... I>
class Test<T, N, IndexSequence<I...>> {
public:
Test(ParamPack<T, I>... v)
: data {v...} {}
private:
Array<T,N> data;
};
But I don't like this "trick" to extract the integer param pack from integerSequece so much, because the integer param pack is now part of the class template. And I actually would prefer it just in a template of the constructor or even without any templates.
For example something like this:
Test(ParamPack<T, makeIndexSequence<N>.getParamPack()>... v)
Or something similar. This would be much cleaner in my opinion.
Is there any way to extract the Param Pack from integerSequence without having the param pack in the class template?
I tried to use the same trick just with a template of a function. Unfortunately, it doesn't work, because functions cannot be partially specialised.
Maybe you even have a completely different approach to have something like std::initializer_list or Param packs of a specific type (I'm aware of the possibility to just use variadic templates and cast it into T, but I would like to avoid this if there is a better solution).
Why do you want to avoid variadic templates? They are the right way to solve these kind of problems.
There is no easy way of generating a dynamic number of parameters from a static value N. All solutions involve specialization for some sort of variadic input (std::index_sequence does exactly the same, it generates variadic template parameters).
I think the most elegant way is to use variadic templates and protect the input using static_assert.
template <typename T, size_t N>
class Test {
public:
template <typename... Ts>
Test(Ts&&... v) : data{std::forward<Ts>(v)...} {
static_assert(sizeof...(Ts) == N, "invalid number of arguments");
static_assert((std::is_same_v<T, Ts> && ...), "wrong type");
}
private:
std::array<T, N> data;
};
Note that I am using std::array in this example. It fails to compile when you pass an invalid type or invalid number of arguments.
Test<int, 2> a{1, 2}; // compiles
Test<int, 2> b{1}; // invalid number of arguments
Test<int, 2> c{1, 2, 3}; // invalid number of arguments
Test<int, 2> d{1, 2.f}; // wrong type
Consider the following structs:
//Implementations provided elsewhere
struct A { A(int i, double d, std::string s); /* ... */ };
struct B { B(double d1, double d2); /* ... */ };
I have two conversion classes whose template signatures look like:
TupleAs< A, int, double, std::string > via1 { ... };
ArrayAs< B, double, 2 > via2 { ... };
Predictably, TupleAs converts a triplet of int,double, and std::string values into an object of type A. Similarly, ArrayAs converts a pair of two double values into an object of type B. (And yes, there are reasons why I cannot call the A and B constructors directly.)
Improving the syntax
I would like to change the syntax so I can do the following:
TupleAs< A(int,double,std::string) > via1 { ... };
ArrayAs< B(double,2) > via2 { ... };
which, I think, is more descriptive of a conversion process. The TupleAs template declaration and corresponding partial specialization would look like this:
template <typename T> struct TupleAs;
template <typename T, typename ... Args>
struct TupleAs<T(Args...)> { ... };
Compiler errors
However, if I try to do something similar with the ArrayAs version:
template <typename T> struct ArrayAs;
template <typename T, typename U, unsigned N>
struct ArrayAs<T(U,N)> { ... };
I get the following errors in clang (3.6) when trying to instantiate it (ArrayAs< B(double,2)> test;):
typeAs.cpp:14:22: error: unknown type name 'N'
struct ArrayAs<T(U,N)>{
^
typeAs.cpp:14:10: warning: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used
struct ArrayAs<T(U,N)>{
^~~~~~~~~~~~~~~
typeAs.cpp:13:45: note: non-deducible template parameter 'N'
template<typename T, typename U, unsigned N>
^
The gcc error diagnostic is a little different, but I won't post it here.
I admit that my templating skills should be better than they are, and I also concede that an analogous std::function<B(double,2)> declaration clearly is nonsense. But can someone tell me why the particular syntax I'm trying to achieve is not allowed? I looked through the C++14 standard and had trouble finding the relevant portion, and I'm having trouble interpreting the clang diagnostic message.
When you specialize TupleAs:
template <typename T, typename ... Args>
struct TupleAs<T(Args...)>
You are basically overloading the notation for a function. You are specializing on a function that takes Args... and returns a T. That is a type. You may not be using that function as a function, or really ever think about it as being a type, but that is what it is.
On the other hand, here:
template <typename T, typename U, unsigned N>
struct ArrayAs<T(U,N)> { ... };
There is no such thing as a function that takes N. It could take unsigned, but it can't take a value. There is just no such reasonable thing. From your example, B(double, 2) simply does not make sense. At best, you could write something that would allow:
template <unsigned N> using size_ = std::integral_constant<size_t, N>;
ArrayAs< B(double,size_<2>) >
Or even:
ArrayAs< B(std::array<double, 2>) >
since now we're back to using types everywhere. Whether you prefer that or not is personal preference.
The key here is that types are first-class citizens when it comes to all things template metaprogramming, and values should be avoided where possible.
template <typename T> struct ArrayAs;
template <typename T, typename U, std::size_t N>
struct ArrayAs<T(std::array<U,N>)> { ... };
works, as would:
template<class T>
struct to_array;
template<class T, size_t N>
struct to_array< T[N] > { using type = std::array<T, N>; };
template<class T>
using arr = typename to_array<T>::type;
then:
ArrayAs< Bob( arr<int[3]> ) > some_var;
live example.
Sadly, directly using ArrayAs< Bob( int[3] ) > doesn't work due to how arrays in function types decay to pointers.
I'm having trouble writing the variadic function that:
1) takes the appropriate number of parameters N-1 (N always > 2) of the appropriate type FooArray<T, N> and
2) allows me to play with them in the fiddly way I need to.
So, given the following template class:
template <typename T, unsigned int N>
class FooArray{
private:
T m_data[N];
};
and the following static variadic template function declaration:
template <typename T, unsigned int N>
class FooArray{
public:
template <typename ... ArgT>
static FooArray<T, N> Weirdness(FooArray<T, N> _arg1, ArgT ... _args);
private:
T m_data[N];
};
and associated implementation in that class:
template <typename T, unsigned int N>
template <typename ... ArgT>
FooArray<T, N>
FooArray<T, N>::Weirdness(T _arg1, ArgT ... _args)
{
static_assert(N > 2, "Not enough Ns for Weirdness.");
static_assert(N - 2 == sizeof... (_args), "Incorrect parameter number.");
FooArray<T, N> result;
// ...
// Put parameters into table of size N * (N-1) maybe?
// Do stuff with that to determine result.
// ...
return result;
}
I want to have the given code (or something like it) return me errors at the marked places and be valid in the marked places too.
int main()
{
FooArray<int, 2> test2parameter;
FooArray<int, 3> test3param1, test3param2;
FooArray<int, 4> test4p1, test4p2, test4p3;
//FooArray<int, 2>::Weirdness(test2parameter); // Should error(and does), not enough N's.
//FooArray<int, 3>::Weirdness(); // Should error(and does), requires 2 parameters.
//FooArray<int, 3>::Weirdness(test2parameter); // Should error(and does), requires 2 parameters of type FooArray<int, 3>.
FooArray<int, 3>::Weirdness(test3param1, test2parameter); // Should error(currently doesn't), all parameters should be FooArray<int, 3>.
FooArray<int, 3>::Weirdness(test3param1, test3param2); // Valid.
//FooArray<int, 4>::Weirdness(); // Should error (and does), requires 3 parameters.
//FooArray<int, 4>::Weirdness(test4p1, test4p2); // Should error (and currently does), requires 3 parameters.
FooArray<int, 4>::Weirdness(test4p1, test4p2, test3param1); // Should error (currently doesn't), all parameters should be FooArray<int, 4>.
FooArray<int, 4>::Weirdness(test4p1, test4p2, test4p3); // Valid.
//FooArray<int, 12>::Weirdness(test12p1, test12p2, test12p3, test12p4, test12p5, test12p6, test12p7, test12p8, test12p9, test12p10, test12p11); // Will be a valid case.
return 0;
}
Re: the initial question, I'm 90% there. Once I have the compiler throwing errors at me for passing the function an incorrect parameter type, the question is answered.
Thank you for your time. (Seriously, stop reading now if you're short on time or are trying to solve your own problem)
Re: Why would you want to do this? I am trying to write an N-Dimensional vector (geometry) cross product function, because I think mathematicians are silly for thinking 3D vectors are the only ones that can have a cross product. I'm certain I can do it if I can write this function. Plus, I'm an indie game developer, this function has interesting uses for me in a handful of games I've got kicking around in my brain.
Bonus material: I have another problem that using variadic templates creates for me. Once I have all these FooArrays inside my Weirdness() function, I have to do a calculation that requires (for every member in the return variable) access to two different members of every passed parameter. So perhaps this is a poor design choice? I'm considering a recursive private static variadic template function to perhaps populate a static array of Ts of size N*(N-1). The existing function doesn't allow this fine fiddle I require. This design bit is all very open ended though and perhaps warrants another question on a more open-ended-question friendly platform. :)
Add one more static assertion:
static_assert(are_same<FooArray<T, N>, ArgT...>::value, "Types do not all match");
And implement are_same<>:
template <typename A1, typename A2, typename ... Rest>
struct are_same
{
enum { value = std::is_same<A1, A2>::value && are_same<A2, Rest...>::value };
};
template <typename A1, typename A2>
struct are_same<A1, A2>
{
enum { value = std::is_same<A1, A2>::value };
};
I would prefer a simpler function declaration and using John Zwinck's method to ensure the parameter types are all correct.
template <typename T, unsigned int N>
template <typename ... ArgT>
FooArray<T, N>
FooArray<T, N>::Weirdness(ArgT ... _args)
{
static_assert(are_same<FooArray<T, N>, ArgT...>::value, "Types do not all match");
static_assert(N > 2, "Not enough Ns for Weirdness.");
static_assert(N - 1 == sizeof... (_args), "Incorrect parameter number.");
Was not exactly sure how to phrase this question or what to search on so if this is the same as another question please close and redirect to the appropriate question.
Suppose
template<typename Type, int Size> class vector
{
Type data[Size];
}
Is it possible to replace a constructor which takes Size number of arguments in template specializations like this
template<typename Type> class vector3<Type,3>
{
Type data[3];
public:
vector3( Type, Type, Type );
}
with something in the non-specialized template class? Like a "varargs constructor" that produces a constructor with Size number of arguments of type Type?
A solution involving C++0x features is fine.
In C++0x you have template typedef finally available!
Disclaimer: nothing has been compiled...
From Wikipedia's article:
template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;
which in your case would yield
template <class Type>
using vector3 = vector<Type, 3>;
I can't tell you how much I craved for this ;)
However it doesn't solve the parameters issue. As mentioned, you could try and use variadic templates here, however I am unsure as to their application in this case. The normal use is with recursive methods and you would need to throw a static_assert in the midst.
Edited to take the comments into account.
template <class Type, size_t Size>
class vector
{
public:
template <class... Args>
vector(Args... args): data({args...})
{
// Necessary only if you wish to ensure that the exact number of args
// is passed, otherwise there could be less than requested
BOOST_MPL_ASSERT_RELATION(sizeof...(Args), ==, Size);
}
private:
T data[Size];
};
Another possibility that is already available is to combine Preprocessor generation with boost::enable_if.
template <class Type, size_t Size>
class vector
{
public:
vector(Type a0, typename boost::enable_if_c< Size == 1 >::type* = 0);
vector(Type a0, Type a1, typename boost::enable_if_c< Size == 2 >::type* = 0);
// ...
};
Using Boost.Preprocessor for the generation makes this easier.
BOOST_PP_REPEAT(MAX_COUNT, CONSTRUCTOR_MACRO, ~);
// where MAX_COUNT is defined to the maximum size you wish
// and CONSTRUCTOR_MACRO actually generates the constructor
#define CONSTRUCTOR_MACRO(z, n, data) \
vector( \
BOOST_PP_ENUM_PARAMS(n, Type a), \
typename boost::enable_if_c< Size == n >::type* = 0 \
);
The implementation of the constructor is left as an exercise for the reader. It's another call to BOOST_PP_REPEAT.
As you can see, it soon gets ugly, so you'll be better off if you can use the variadic template version.
There's a further solution to your problem: Using variadic template parameters in the initializer list
template<typename T, unsigned int N>
struct vector {
T data[N];
template<typename... Args>
vector(Args... args) : data({args...}) { }
};
However the number of arguments only needs to be less or equal than N and their types only needs to be convertible to T.
Is it possible to replace a constructor which takes Size number of arguments in template specializations like this
Not without a ton of repetitive, mechanical code, and the maximum size will be limited by the number of times you repeat yourself. Case in point: boost::tuple (which might have exactly the functionality what you want).
In C++0x this won't be a problem thanks to variadic templates.
First you should consider using std::array. It doesn't meet all your requirements but it's close enough that if the differences don't matter you can save yourself a lot of work. The problem is this cheap version will have construct that accepts 2 arguments as well as 3.
template< typename T>
using Vector3 = std::array<T,3>;
Vector3 v1{1,2,3};
Vector3 v2{1,2}; // it sounds like you want to disallow this case.
Otherwise you can create a custom class that acts a lot like std::array, except with a more picky constructor.
template<typename T, std::size_t SIZE>
class Vector
{
public:
template< typename ... Args >
Vector( Args ... args ) :
data({args...})
{
static_assert( sizeof...(Args) == SIZE,
"Incorrect number of arguments passed to Vector constructor");
}
/* lots of extra code here to add std::array -like methods */
private:
// could use std::array here as well.
T data[3];
};
template< typename T >
using Vector3 = Vector<T,3>;
Vector3 v1(1,2,3);
Vector3 v2(1,2); // error at compile time.