Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve variadic template parameters without auxillary function template?

Suppose I have

template<int ...>
struct Ints { };

class MyClass
{
public:
    Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};

What I want to do is simple.

template <class T>
vector<int> MyFunc1(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

Somewhat like this. (Here MyClass can be one example of T.) Apparently, for compiler S... seems to invalid.

template <class T, int... S>
vector<int> MyFunc2(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

This doesn't work either. I think from get() the S... becomes specific and automatically deducible, but compiler doesn't recognize it. (I'm not sure but C++ doesn't deduce template parameters looking inside the function, but only arguments and return type)

The only way I've found is to use another function that finds out what int... was.

template <int ...S>
vector<int> temp(Ints<S...> not_used)
{
    return { S... };
}

template <class T>
vector<int> MyFunc3(T& x)
{
    auto result = x.get();
    return temp(result);
}

It works well, but requires another extra helper function that does nothing but just provides syntactically clear way to match the S... using templates.

I really want to do this just in single function. Do I really have to define auxiliary function whenever I want to retrieve parameter packs, in every time?

Edit : Ints and MyFunc are just toy example. I want to know general method to retrieve template parameters!

like image 657
i.stav Avatar asked May 02 '19 06:05

i.stav


People also ask

Which parameter is allowed for non-type template?

Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.


3 Answers

What would the ideal interface look like?

If given a variable of type Ints<S...>, we'd ideally be able to use S... with as little modification as possible.

In this case, we can design an interface which allows us to use the parameter pack as an input to a variadic function or lambda, even going so far as to reuse the values as template parameters.

Proposed interface [Dynamic case / ints passed as values]

Both the static case and the dynamic case have similar interfaces, however the dynamic case is slightly cleaner, and makes for a better introduction. Given the variable and a function, we apply the function with the parameter pack contained in the definition of the variable.

Ints<1, 2, 3> ints;

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = ints | [](auto... S) { return std::vector {S...}; };

// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S...}; }; 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = ints | [](auto... S) { return std::make_tuple(S...); };

// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S + ...); }; 

It's a simple, unified syntax which allows us to take S and use it as a parameter pack.

Writing this interface

This part is pretty straight-forward too. We take a variable of type Ints<S...>, and a function, and apply the function with S....

template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
    return f(S...); 
}

Proposed interface [Static case / ints usable as template parameters]

As stated before, the static case has a similar interface to the dynamic case, and it won't be too much of a stretch conceptually. From a user standpoint, the only difference is that instead of using S... as the parameter pack, well useS.value...` as the pack.

For each value, we want to encapsulate it in a corresponding type templated on the value. This allows us to access it in a constexpr context.

template<int Value>
struct ConstInt {
    constexpr static int value = Value;
};

To differentiate it from the dynamic case, I'm going to overload / instead of |. Otherwise, they behave similarly. The implementation is pretty much the same as the dynamic case, except that the values are wrapped in the ConstInt class, and each will have it's own type.

template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
    return func(ConstInt<S>()...); 
}

Using this interface statically

C++ allows us to access static members of a class using the same syntax as non-static members, without losing constexpr status.

Let's say I have some ConstInt with a value of 10. I can directly use I.value as a template parameter, or I can use decltype(I)::value:

// This is what'll be passed in as a parameter
ConstInt<10> I;

std::array<int, I.value> arr1;
std::array<int, decltype(I)::value> arr2; 
// Both have length 10

Expanding a parameter pack is therefore extraordinarily straight-forward, and it ends up being almost identical to the dynamic case, the only difference being the .value appended to S. Shown below are the examples from the dynamic case, this time using the static case syntax:

Ints<1, 2, 3> ints;

// Get a vector from ints
auto vec = ints | [](auto... S) { return std::vector {S.value...}; };

// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S.value...}; }; 

// Get a tuple from ints
auto tup = ints | [](auto... S) { return std::make_tuple(S.value...); };

// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S.value + ...); }; 

So what's new? Because value is constexpr, S.value can be used trivially as a template parameter. In this example, we use S.value to index into a tuple using std::get:

auto tupA = std::make_tuple(10.0, "Hello", 3); 

auto indicies = Ints<2, 0, 1>{};

// tupB = {3, 10.0, "Hello"}
auto tupB = indicies / [&](auto... S) { 
    return std::make_tuple(std::get<S.value>(tupA)...);
};

And in this example, we square every element in a sequence, and return a new sequence:

auto ints = Ints<0, 1, 2, 3, 4, 5>(); 

// ints_squared = Ints<0, 1, 4, 9, 16, 25>(); 
auto ints_squared = ints / [](auto... S) {
    return Ints<(S.value * S.value)...>(); 
};

Alternative solution that avoids operator overloading

If you want to avoid operator overloading, we can take some inspiration from functional programming and handle things with an unpack function, written like so:

template<int... vals>
auto unpack(Ints<vals...>) {
    return [](auto&& f) { return f(vals...); }; 
}

// Static case
template<int... vals>
auto unpack_static(Ints<vals...>) {
    return [](auto&& f) { return f(ConstInt<vals>()...); }; 
}

So what is unpack? This function takes a bunch of values, and it returns a function which takes another function and applies the function with the vals as inputs.

The unpack function allows us to apply those values to a different function, as parameters.

We can assign the result to a variable called apply_ints, and then we can use apply_ints to handle all the specific use-cases:

Ints<1, 2, 3> ints; //this variable has our ints

auto apply_ints = unpack(ints); // We use this function to unpack them

We can re-write the examples from before, this time using apply_ints:

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = apply_ints([](auto... S) { return std::vector {S...}; });

// Get an array from ints
// arr = {1, 2, 3}
auto arr = apply_ints([](auto... S) { return std::array {S...}; }); 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = apply_ints([](auto... S) { return std::make_tuple(S...); });

// Get sum of ints using a fold expression
auto sum = apply_ints([](auto... S) { return (S + ...); }); 

Appendix

This appendix gives a brief overview showing how to use this syntax more generally (such as when working with multiple separate parameter packs).

Bonus example: pairing up values from two separate packs

To give you a better idea of the flexibility of this interface, here's an example where we use it to pair up values from two separate packs.

Ints<1, 2, 3> intsA;
Ints<10, 20, 30> intsB;

// pairs = {{1, 10}, {2, 20}, {3, 30}}
auto pairs = intsA | [&](auto... S1) {
    return intsB | [&](auto... S2) {
        return std::vector{ std::pair{S1, S2}... }; 
    };
};

NB: MSVC and GCC both compile this example without issues, however clang chokes up on it. I assume MSVC and GCC are correct, but I don't know for sure.

Bonus example: Getting a 2-dimensional times table

This example is a little more complicated, but we can also create 2-dimensional arrays of values that draw from all combinations of values from separate packs.

In this case, I use it to create a times table.

Ints<1, 2, 3, 4, 5, 6, 7, 8, 9> digits;

auto multiply = [](auto mul, auto... vals) {
    return std::vector{(mul * vals)...}; 
};

auto times_table = digits | [&](auto... S1) {
    return digits | [&](auto... S2) {
        return std::vector{ multiply(S1, S2...)... };
    };
};
like image 84
Alecto Irene Perez Avatar answered Oct 19 '22 05:10

Alecto Irene Perez


In C++2a, you might use templated lambda to define your helper inside your function, something like:

auto v = []<std::size_t...Is>(std::index_sequence<Is...>){return std::vector{Is...};}(seq);
//         ^^^^^^^^^^^^^^^^^^ New in C++2a

Demo

like image 20
Jarod42 Avatar answered Oct 19 '22 05:10

Jarod42


If you don't use/create a helper template, you need some other way to provide the values.

The most simple, canonical and general way I can think of, is to put them in that same class scope, so that your Ints struct becomes:

template<int ...ints>
struct Ints {
  constexpr static std::initializer_list<int> vals = {ints...};
};

Since it is constexpr it should be evaluated at compile time and not incur a runtime cost. Now you will be able do something like:

return std::vector<int>(ints.vals);
like image 1
darune Avatar answered Oct 19 '22 06:10

darune