Extract Parameter Pack - c++

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

Related

Find the type of a non-type template parameter, based on a template class

Given a class of the form:
template <int A, int B, int C>
struct Functor {
static int go() {
return A*B*C;
}
};
I need to produce a parameter pack / tuple / etc of the types of the parameters for Functor. That is, I want to be able to do things like:
// Imagining that I have many Functor classes...
using FirstArgType = TypeAt<Functor, 1>::T;
FirstArgType t {4};
Essentially, I need to go from a parameter pack of values, to a parameter pack of the TYPES of those values of an unspecialized template class - that is, Functor and not Functor<1, 2, 3>. I naively starting with things that looked like:
template <template <auto...Values> typename Class>
struct ClassInfo {
using Tuple = std::tuple<decltype(Values)...>;
};
However, nested template-template parameters can't be accessed like this (error: use of undeclared identifier 'Values'). Note that when I use auto...Values as top-level template parameters, this tuple technique works fine to discover the types - the problem is in extracting the template params for Class.
For every formulation I've tried, I need to at some point specify a fully specialized type (e.g. Functor<1, 2, 3>) in order to figure out the types - but I am trying to operate on the template class Functor, not the specialization of it Functor<n,n,n> - I need template code to operate on every specialization, e.g. Functor<1, 2, 3> and Functor<4, 5, 6>, not just find the types of a specific specialization.
On one hand: I feel like I'm attempting something fundamentally impossible with C++ templates - in way that I don't understand - which is why every formulation I can think of fails.
On the other hand: CLEARLY the types of the template params for Functor are well-known at compile time, so I imagine there SHOULD be a way to discover these.
A solution would be great, but I'm equally happy to hear about strategies/techniques/design patterns for dealing with template-template parameters that I'm not familiar with (I wouldn't consider myself a pro here).
Essentially, I need to go from a parameter pack of values, to a
parameter pack of the TYPES of those values.
You can use template partial specialization to extract the type of non-type template parameter, something like this:
#include <tuple>
template<auto... args>
struct Functor {};
template <class>
struct ClassInfo {};
template <auto... args>
struct ClassInfo<Functor<args...>> {
using type = std::tuple<decltype(args)...>;
};
using F = Functor<0, 42u, 'a', true>;
static_assert(
std::is_same_v<ClassInfo<F>::type, std::tuple<int, unsigned, char, bool>>);
Demo.
Do you mean something like this?
#include <tuple>
template <auto... Values>
struct GenericFunctor {
using Tuple = std::tuple<decltype(Values)...>;
};
using SpecificFunctor = GenericFunctor<short(1), long(2)>;
// Extracts specific template types from functors.
template<class Functor, int i>
using ArgType = decltype(std::get<i>( std::declval<typename Functor::Tuple>() ));
// Instantiate the specific arg type.
static ArgType<SpecificFunctor, 1> t { 1 };

Recursively replace type by other type in a templated type

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

Variable number of constructor parameters depending on integer template

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} {}
};

variadic templates same number of function arguments as in class

How to define method signature so it will accept same number of arguments as variadic template class definition? For example how to define an Array class:
template<typename T, int... shape>
class Array
{
public:
T& operator () (???);
};
So you will be able to call it like this:
Array<int, 3, 4, 5> a;
a(1, 2, 3) = 2;
template<class T, int...Shape>
class Array {
template<int>using index_t=int; // can change this
public:
T& operator()(index_t<Shape>... is);
};
or:
template<class T, int...Shape>
class Array {
public:
T& operator()(decltype(Shape)... is);
};
or:
template<class T, int...Shape>
class Array {
public:
T& operator()(decltype(Shape, int())... is);
};
if you want to be able to change the type of the parameter to be different than Shape.
I find the decltype harder to understand a touch than the using, especially if you want to change the type of the parameter to be different than int.
Another approach:
template<class T, int...Shape>
class Array {
public:
template<class...Args,class=typename std::enable_if<sizeof...(Args)==sizeof...(Shape)>::type>
T& operator()(Args&&... is);
};
which uses SFINAE. It does not enforce that the Args are integer types however. We could add another clause if we wanted to (that all of the Args are convertible to int, say).
Yet another approach is to have your operator() take a package of values, like a std::array<sizeof...(Shape), int>. Callers would have to:
Array<double, 3,2,1> arr;
arr({0,0,0});
use a set of {}s.
A final approach would be:
template<class T, int...Shape>
class Array {
public:
template<class...Args>
auto operator()(Args&&... is) {
static_assert( sizeof...(Args)==sizeof...(Shapes), "wrong number of array indexes" );
}
};
where we accept anything, then generate errors if it is the wrong number of arguments. This generates very clean errors, but does not do proper SFINAE operator overloading.
I would recommend tag dispatching, but I don't see a way to make it much cleaner than the SFINAE solution, with the extra decltype and all, or better error messages than the static_assert version on the other hand.
I assume you want your arguments to be all of the same type, probably using an integer type (I'll just use int). An easy approach is to leverage the parameter pack you already have:
template <int>
struct shape_helper { typedef int type; };
template <typename T, int... Shape>
class Array
{
public:
T& operator()(typename shape_helper<Shape>::type...);
};

Is it possible to write a static variadic template function, in a template class, that's able to take N parameters of type T?

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.");