I have the following pseudo code:
template <typename... Ts>
void f(int index) {
std::vector<std::function<void(void)>> funcs;
funcs.push_back([](){ std::cout << typeid(type_1).name() << std::endl; });
funcs.push_back([](){ std::cout << typeid(type_2).name() << std::endl; });
funcs.push_back([](){ std::cout << typeid(type_3).name() << std::endl; });
funcs.push_back([](){ std::cout << typeid(type_4).name() << std::endl; });
funcs[index]();
}
Imagine that the Ts... parameter pack holds type_1, type_2, type_3 and type_4.
how can I expand the parameter pack in order to achieve something like this? I mean - how can I get 4 push_back() calls if there are 4 parameters in the template pack, and also have the different types in the different lambdas? I don't know the syntax..
And can I actually get some sort of an array of such functions at compile time, so there are no push_backs at runtime?
C++17 solution is ok, but C++14 is best.
For C++17, something like this, I suppose
(funcs.push_back([](){ std::cout << typeid(Ts).name() << std::endl; }), ...);
or, better (IMHO), using emplace_back()
(funcs.emplace_back([](){ std::cout << typeid(Ts).name() << std::endl; }), ...);
But remeber that is
std::vector<std::function<void(void)>>
not
std::vector<std::function<void>>
In C++14 (and C++11) you can obtain something similar with the trick of intialization of the unused array; the function can be written as
template <typename ... Ts>
void f (int index)
{
using unused = int[];
std::vector<std::function<void(void)>> funcs;
(void)unused { 0, (funcs.emplace_back([]()
{ std::cout << typeid(Ts).name() << std::endl; }), 0)... };
funcs[index]();
}
Update.
From re-reading the question I think you just want to call the function once for the I'th type.
I which case it's trivial at compile time:
#include <array>
#include <type_traits>
#include <iostream>
#include <string>
template <class T>
void show_type()
{
std::cout << typeid(T).name() << std::endl;
}
template <typename... Ts>
void f(int index) {
using function_type = void(*)();
constexpr auto size = sizeof...(Ts);
constexpr std::array<function_type, size> funcs =
{
&show_type<Ts>...
};
funcs[index]();
}
int main()
{
for(int i = 0 ; i < 3 ; ++i)
f<int, double, std::string>(i);
}
example output:
i
d
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Something along these lines, perhaps:
template <typename... Ts>
void f(int index) {
int i = 0;
auto _ = {
(index == i++ ? ((std::cout << typeid(Ts).name() << std::endl) , 0) : 0) ...
};
}
Demo
If all you want to do is do something for the nth type in a template parameter pack, where n is a runtime variable, then the vector + function approach isn't really great. Better to add an index sequence in there and fold:
template <typename T> struct tag_t { using type = T; };
template <typename T> constexpr inline tag_t<T> tag{};
template <class F, size_t... Is, typename... Tags>
void match(F f, size_t i, std::index_sequence<Is...>, Tags... tags) {
auto inner = [&](auto tag) { f(tag); return true; };
bool matched = ((i == Is && inner(tags)) || ...);
if (!matched) {
// failure case?
}
}
template <typename... Ts, class F>
void match(F f, size_t i) {
return match(f, i, std::index_sequence_for<Ts...>(), tag<Ts>... );
}
template <typename... Ts>
void foo(int index) {
match<Ts...>([](auto tag){
std::cout << typeid(typename decltype(tag)::type).name() << std::endl;
}, index);
}
This construction allows you to add a failure case, where you might call the passed-in function with some special type:
struct failure { };
template <class F, size_t... Is, typename... Tags>
void match(F f, size_t i, std::index_sequence<Is...>, Tags... tags) {
auto inner = [&](auto tag) { f(tag); return true; };
bool matched = ((i == Is && inner(tags)) || ...);
if (!matched) {
f(failure{});
}
}
template <typename... Ts>
void foo(int index) {
match<Ts...>(overload(
[](auto tag){
std::cout << typeid(typename decltype(tag)::type).name() << std::endl;
},
[](failure ) { /* ... */ }
), index);
}
Related
I don't know if there is a good and clean way to index variadic arguments when unpacking tuple-like objects into callable handlers, i.e. when using std::apply.
Here is a not perfect, but rather clean solution:
const auto animals = std::make_tuple("cow", "dog", "sheep");
// handwritten, stateful, bad...
std::apply([](const auto& ... str){
const auto print = [](const auto& str, size_t index){
std::cout << index << ": " << str << '\n';
};
// this should not be done by the user!!!
size_t i = 0;
(print(str, i++), ...);
}, animals);
This solution is cleaner than using overloads with std::index_sequence since you don't have to write any code outside lambda's scope. Templates are not allowed within a block scope, so one would need to create some helper class outside.
It is bad, since there is a mutable state that is created by user. There should be no such thing, index should be available implicitly and on demand.
Here is what I think is desired and what I managed to implement so far:
const auto animals = std::make_tuple("cow", "dog", "sheep");
// desired
// JavaScript-style destructuring.
// C++ structured bindings are not allowed as arguments
// apply([](auto ... {value, index}){ ... }, animals);
// still bad, but better - index is implicit and constant
std::apply(indexed([](auto ... indexedValue){
const auto print = [](const auto& indexedValue){
const auto &[index, value] = indexedValue;
std::cout << index << ": " << value << '\n';
};
(print(indexedValue), ...);
}), animals);
C++ does not allow to have structured bindings as function arguments, and this is very unfortunate. Anyways, I consider this api better, than incrementing a counter by hand or writing some boilerplate helper.
All you have to do is to follow the damn train wrap your callable into indexed() function.
And it does not require any modifications on STL's part.
However, my implementation is very far from optimal. It results in far more instructions than the first example: https://godbolt.org/z/3G4doao39
Here is my implementation for indexed() function which I would like to be corrected.
#include <cstddef>
#include <type_traits>
#include <tuple>
namespace detail {
template <size_t I, typename T>
struct _indexed
{
constexpr static size_t index = I;
T value;
constexpr _indexed(std::integral_constant<size_t, I>, T t)
: value(t)
{}
template <size_t Elem>
friend constexpr auto get(const detail::_indexed<I, T>& v) noexcept ->
std::tuple_element_t<Elem, detail::_indexed<I, T>>{
if constexpr (Elem == 0)
return I;
if constexpr (Elem == 1)
return v.value;
}
};
template <size_t I, typename T>
_indexed(std::integral_constant<size_t, I>, T) -> _indexed<I, T>;
template <typename CRTP>
class _add_indices
{
public:
template <typename ... Args>
constexpr decltype(auto) operator()(Args &&... args) const noexcept {
return (*this)(std::make_index_sequence<sizeof...(Args)>(), std::forward<Args>(args)...);
}
private:
template <typename ... Args, size_t ... I>
constexpr decltype(auto) operator()(std::index_sequence<I...>, Args ... args) const noexcept {
// does not compile
// return std::invoke(&CRTP::callable, static_cast<CRTP const&>(*this),
// _indexed(std::integral_constant<size_t, I>{}, std::forward<Args>(args))...);
return static_cast<const CRTP&>(*this).callable(_indexed(std::integral_constant<size_t, I>{}, std::forward<Args>(args))...);
}
};
}
template <size_t I, typename T>
struct std::tuple_size<detail::_indexed<I, T>> : std::integral_constant<size_t, 2> {};
template <size_t I, typename T>
struct std::tuple_element<0, detail::_indexed<I, T>>
{
using type = size_t;
};
template <size_t I, typename T>
struct std::tuple_element<1, detail::_indexed<I, T>>
{
using type = T;
};
template <typename Callable>
constexpr auto indexed(Callable c) noexcept{
struct _c : detail::_add_indices<_c> {
Callable callable;
};
return _c{.callable = c};
}
// api:
// apply(indexed([](auto ... indexedValue){}), tuple);
If I correctly understand what do you want... it seems to me that you only need a class/struct as follows
template <typename Callable>
struct indexed_call
{
Callable c;
template <std::size_t ... Is, typename ... As>
constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
return c(std::pair{Is, std::forward<As>(as)}...);
}
template <typename ... As>
constexpr auto operator() (As && ... as) const {
return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
}
};
and an explicit, trivial, deduction guide
template <typename C>
indexed_call(C) -> indexed_call<C>;
and the call change a little as follows
std::apply(indexed_call{[](auto ... indexedValue){
const auto print = [](const auto& iV){
const auto &[index, value] = iV;
std::cout << index << ": " << value << '\n';
};
(print(indexedValue), ...);
}}, animals);
or, if you prefer, also the call can be simplified
std::apply(indexed_call{[](auto ... iV){
((std::cout << iV.first << ": " << iV.second << '\n'), ...);
}}, animals);
The following is a full compiling example
#include <type_traits>
#include <iostream>
#include <tuple>
template <typename Callable>
struct indexed_call
{
Callable c;
template <std::size_t ... Is, typename ... As>
constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
return c(std::pair{Is, std::forward<As>(as)}...);
}
template <typename ... As>
constexpr auto operator() (As && ... as) const {
return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
}
};
template <typename C>
indexed_call(C) -> indexed_call<C>;
int main(){
const auto animals = std::make_tuple("cow", "dog", "sheep");
std::apply(indexed_call{[](auto ... indexedValue){
const auto print = [](const auto& iV){
const auto &[index, value] = iV;
std::cout << index << ": " << value << '\n';
};
(print(indexedValue), ...);
}}, animals);
std::apply(indexed_call{[](auto ... iV){
((std::cout << iV.first << ": " << iV.second << '\n'), ...);
}}, animals);
}
----- EDIT -----
The OP writes
I don't think that utilizing std::pair is semantically correct. Would be better to have .value, .index
I usually prefer to use standard components, when functional equivalents, but if you want something with a value and a index components, you can add a simple struct (name it as you prefer)
template <typename V>
struct val_with_index
{
std::size_t index;
V value;
};
and another trivial explicit deduction guide
template <typename V>
val_with_index(std::size_t, V) -> val_with_index<V>;
then you have to modify the call() method to use it instead std::pair
template <std::size_t ... Is, typename ... As>
constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
return c(val_with_index{Is, std::forward<As>(as)}...);
} // ......^^^^^^^^^^^^^^
and now works for the double lambda case
For the simplified case, obviously you have to change first and second with index and value
std::apply(indexed_call{[](auto ... iV){
((std::cout << iV.index << ": " << iV.value << '\n'), ...);
}}, animals); // ....^^^^^...............^^^^^
Again the full compiling example
#include <type_traits>
#include <iostream>
#include <tuple>
template <typename V>
struct val_with_index
{
std::size_t index;
V value;
};
template <typename V>
val_with_index(std::size_t, V) -> val_with_index<V>;
template <typename Callable>
struct indexed_call
{
Callable c;
template <std::size_t ... Is, typename ... As>
constexpr auto call (std::index_sequence<Is...>, As && ... as) const {
return c(val_with_index{Is, std::forward<As>(as)}...);
}
template <typename ... As>
constexpr auto operator() (As && ... as) const {
return call(std::index_sequence_for<As...>{}, std::forward<As>(as)...);
}
};
template <typename C>
indexed_call(C) -> indexed_call<C>;
int main(){
const auto animals = std::make_tuple("cow", "dog", "sheep");
std::apply(indexed_call{[](auto ... indexedValue){
const auto print = [](const auto& iV){
const auto &[index, value] = iV;
std::cout << index << ": " << value << '\n';
};
(print(indexedValue), ...);
}}, animals);
std::apply(indexed_call{[](auto ... iV){
((std::cout << iV.index << ": " << iV.value << '\n'), ...);
}}, animals);
}
I wanna implement a Print function which works this:
Print<1, 3>("Hello", "World");
and I hope that it will print "Hello" one time and "World" 3 times.I wonder how to implement it.
Below is my stupid code, of course it failed when compiling:
template <unsigned int n, unsigned int ...n_next,
typename T, typename ...Ts>
void Print(T & t, Ts & ... ts)
{
for(int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
Print<n_next..., ts...>(ts...);
}
template <unsigned int n, typename T>
void Print(T & t)
{
for(int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
}
This will make it:
template <unsigned int n, typename T>
void Print(T&& t)
{
for(int i = 0; i < n; i++)
{
std::cout << std::forward<T>(t) << " ";
}
std::cout << std::endl;
}
template <std::size_t Idx1, std::size_t... Idx, class T, class... Ts>
void Print(T&& t, Ts&& ... ts) {
Print<Idx1>(std::forward<T>(t));
using expand = int[];
(void)expand{(Print<Idx>(std::forward<Ts>(ts)), 0) ...};
}
I also propose a completely different solution that avoid at all recursion and for() loops.
It simulate template folding in C++14 in initialization of an unused C-style array.
First the main Print(), that expand the variadic lists calling a Print_h() helper function, passing to it the values and list (index sequence) correspinding to numbers of iteration for every value
template <std::size_t ... Ns, typename ... Ts>
void Print (Ts ... ts)
{
using unused=int[];
(void)unused { 0, (Print_h(std::make_index_sequence<Ns>{}, ts), 0)... };
}
Next the helper function that uses the same trick for multiple printing
template <std::size_t ... Is, typename T>
void Print_h (std::index_sequence<Is...>, T const & t)
{
using unused=std::size_t[];
(void)unused { 0, (std::cout << t << " ", Is)... };
std::cout << std::endl;
}
The following is the full compiling C++14 example
#include <utility>
#include <iostream>
template <std::size_t ... Is, typename T>
void Print_h (std::index_sequence<Is...>, T const & t)
{
using unused=std::size_t[];
(void)unused { 0, (std::cout << t << " ", Is)... };
std::cout << std::endl;
}
template <std::size_t ... Ns, typename ... Ts>
void Print (Ts ... ts)
{
using unused=int[];
(void)unused { 0, (Print_h(std::make_index_sequence<Ns>{}, ts), 0)... };
}
int main ()
{
Print<1u, 3u>("hello", "world");
}
If you can't use C++14 but only C++11, isn't difficult to develop substitutes for std::make_index_sequence and std::index_sequence (both available only from C++14).
Obviously in C++17 you can use template folding simplifying the functions as follows
template <std::size_t ... Is, typename T>
void Print_h (std::index_sequence<Is...>, T const & t)
{
((std::cout << t << " ", (void)Is), ...);
std::cout << std::endl;
}
template <std::size_t ... Ns, typename ... Ts>
void Print (Ts ... ts)
{ (Print_h(std::make_index_sequence<Ns>{}, ts), ...); }
I see four problems in your code
(1) the recursive call
Print<n_next..., ts...>(ts...);
is wrong because you have to use Ts... types in template argument list, not ts... values
Print<n_next..., Ts...>(ts...);
or, better (because permit a trick I explain next) without explicating the types
Print<n_next...>(ts...);
(2) is better if you receive as const references the values
template <unsigned int n, unsigned int ...n_next,
typename T, typename ...Ts>
void Print(T const & t, Ts ... ts)
// ..........^^^^^
otherwise you can't call Print() with constant values as follows
Print<1u, 3u>(1, "world");
(3) better use unsigned value for indexes in for loops because you have to test they with unsigned values (minor problem)
// ..VVVVVVVV
for (unsigned int i = 0; i < n; i++)
(4) you have to place the ground case for Print() (the one that receive only a value) before the recursive case.
I suggest to substitute they with
template <typename = void>
void Print ()
{ }
because, this way, all prints are done in recursive version an you don't need to repeat equal code in two different function (but you have to call Print<n_next...>(ts...); the recursion.
So I propose to modify your code as follows
#include <iostream>
template <typename = void>
void Print ()
{ }
template <unsigned int n, unsigned int ...n_next,
typename T, typename ...Ts>
void Print(T const & t, Ts ... ts)
{
for(auto i = 0u; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
Print<n_next...>(ts...);
}
int main ()
{
Print<1u, 3u>("hello", "world");
}
You can make your code work by just swapping the declarations of the two overloads and removing the ts... template argument in the recursive call:
template <unsigned int n, typename T>
void Print(T & t)
{
for(unsigned int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
}
template <unsigned int n, unsigned int ...n_next,
typename T, typename ...Ts>
void Print(T & t, Ts & ... ts)
{
for(unsigned int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
Print<n_next...>(ts...);
}
(also, be consistant with signedness)
Demo
Furthermore, you don't need to duplicate the printing part, just call the other overload:
template <unsigned int n, typename T>
void Print(T & t)
{
for(unsigned int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
}
template <unsigned int n, unsigned int ...n_next,
typename T, typename ...Ts>
void Print(T & t, Ts & ... ts)
{
Print<n>(t);
Print<n_next...>(ts...);
}
Alternatively, if you can use C++17 for fold expressions, you can do the following (use forwarding references and std::forward if you need to):
template<typename T>
void Print(unsigned int n, T& t)
{
for(unsigned int i = 0; i < n; i++)
{
std::cout << t << " ";
}
std::cout << std::endl;
}
template<unsigned int... Ns, typename... Ts>
void Print(Ts&... ts)
{
(Print(Ns, ts), ...);
}
Demo
I'm using C++17. I'd like to get an element of a tuple that satisfies some type trait. It would be amazing if the trait could be supplied generically, but I'd be satisfied with a specific function for a certain trait. Usage might look something like this:
auto my_tuple = std::make_tuple { 0.f, 1 };
auto basic = get_if_integral (my_tuple);
auto fancy = get_if<std::is_floating_point> (my_tuple);
std::cout << basic; // '1'
std::cout << fancy; // '0.f'
Ideally this would fail to compile if more than one element satisfies the trait, like std::get (std::tuple).
Here's a surprisingly simple way without using recursion:
template <template <typename...> typename T, typename... Ts>
constexpr int index_of_integral(const T<Ts...>&)
{
const bool a[] = { std::is_integral_v<Ts>... };
for (int i = 0; i < sizeof...(Ts); ++i) if (a[i]) return i;
return -1;
}
template <typename T>
constexpr decltype(auto) get_if_integral(T&& t)
{
return std::get<index_of_integral(t)>(std::forward<T>(t));
}
int main()
{
constexpr auto t = std::make_tuple(3.14, 42, "xyzzy");
static_assert(get_if_integral(t) == 42);
}
It could easily be extended to be parametrized on the trait.
The only things that make it C++17 are the is_integral_v variable template and the single-argument static_assert. Everything else is C++14.
Note that in C++20 the for loop could be replaced with std::find and std::distance.
Ideally it should throw an exception instead of returning -1, but compilers don't seem to like that.
Inspired by this answer.
If I understand correctly what you want... I propose an helper struct gf_h ("get first helper") as follows
template <std::size_t, bool ...>
struct gf_h
{ };
template <std::size_t I, bool ... Bs>
struct gf_h<I, false, Bs...> : public gf_h<I+1u, Bs...>
{ };
template <std::size_t I, bool ... Bs>
struct gf_h<I, true, Bs...> : public std::integral_constant<std::size_t, I>
{ };
and a couple of functions that use it:
template <typename ... Us,
std::size_t I = gf_h<0, std::is_integral<Us>::value...>::value>
auto get_first_integral (std::tuple<Us...> const & t)
{ return std::get<I>(t); }
template <typename ... Us,
std::size_t I = gf_h<0, std::is_floating_point<Us>::value...>::value>
auto get_first_floating (std::tuple<Us...> const & t)
{ return std::get<I>(t); }
Observe that are SFINAE enabled/disabled functions, so are enabled only if there is an integral (or float) value in the tuple
The following is a full compiling example
#include <tuple>
#include <iostream>
template <std::size_t, bool ...>
struct gf_h
{ };
template <std::size_t I, bool ... Bs>
struct gf_h<I, false, Bs...> : public gf_h<I+1u, Bs...>
{ };
template <std::size_t I, bool ... Bs>
struct gf_h<I, true, Bs...> : public std::integral_constant<std::size_t, I>
{ };
template <typename ... Us,
std::size_t I = gf_h<0, std::is_integral<Us>::value...>::value>
auto get_first_integral (std::tuple<Us...> const & t)
{ return std::get<I>(t); }
template <typename ... Us,
std::size_t I = gf_h<0, std::is_floating_point<Us>::value...>::value>
auto get_first_floating (std::tuple<Us...> const & t)
{ return std::get<I>(t); }
int main()
{
auto tup1 = std::make_tuple(3.f, 2., 1, 0);
std::cout << get_first_integral(tup1) << std::endl; // 1
std::cout << get_first_floating(tup1) << std::endl; // 3
auto tup2 = std::make_tuple("abc", 4, 5);
std::cout << get_first_integral(tup2) << std::endl; // 4
// std::cout << get_first_floating(tup2) << std::endl; // error
auto tup3 = std::make_tuple("xyz", 6., 7.f);
// std::cout << get_first_integral(tup3) << std::endl; // error
std::cout << get_first_floating(tup3) << std::endl; // 6
}
Ok, I figured out a way to accomplish this in a way that is not generic over the trait, but that's good enough for my current purpose. Using if constexpr this really doesn't look too bad. I'm sure this isn't hugely idiomatic, but it works for me:
template <std::size_t Idx, typename... Us>
auto& get_if_integral_impl (std::tuple<Us...>& t)
{
static_assert (Idx < std::tuple_size_v<std::tuple<Us...>>,
"No integral elements in this tuple.");
if constexpr (std::is_integral<std::tuple_element_t<Idx, std::tuple<Us...>>>::value)
return std::get<Idx> (t);
else
return get_if_integral_impl<Idx + 1> (t);
}
template<typename... Us>
auto& get_if_integral (std::tuple<Us...>& t)
{
return get_if_integral_impl<0> (t);
}
auto tup = std::make_tuple (3.f, 2., 1, 0);
std::cout << get_if_integral (tup); // '1'
My use case is a little more complex, involving returning the first nested tuple which itself contains another type, but this should convey the basic idea.
Most samples of c++ books leverage recursively mechanism to print std::tuple.
Is it possible to print std::tuples iteratively by leverage sizeof...(Typename)?
For example, the function signature is like below:
template<typename... Ts>
constexpr void PrintTuple(std::tuple<Ts...>& tuple)
Then I could use sizeof...(Ts) to know how many elements in the tuple and then
I could use std::get< i >(tuple) to retrieve the individual element?
Here's one of the possible solutions:
#include <cstddef>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
template <typename T, std::size_t ...I, typename F>
void tuple_foreach_impl(T &&tuple, std::index_sequence<I...>, F &&func)
{
// In C++17 we would use a fold expression here, but in C++14 we have to resort to this.
using dummy_array = int[];
dummy_array{(void(func(std::get<I>(tuple))), 0)..., 0};
}
template <typename T, typename F> void tuple_foreach(T &&tuple, F &&func)
{
constexpr int size = std::tuple_size<std::remove_reference_t<T>>::value;
tuple_foreach_impl(std::forward<T>(tuple), std::make_index_sequence<size>{},
std::forward<F>(func));
}
int main()
{
auto x = std::make_tuple("Meow", 1, 2.3);
tuple_foreach(x, [](auto &&value)
{
std::cout << value << ' ';
});
// Prints:
// Meow
// 1
// 2.3
}
With tuple_foreach making a proper printer should be simple.
template <typename T> void print_tuple(const T &tuple)
{
std::cout << '{';
tuple_foreach(tuple, [first = true](auto &value) mutable
{
if (!first)
std::cout << "; ";
else
first = 0;
std::cout << value;
});
std::cout << '}';
}
// ...
print_tuple(std::make_tuple("Meow", 1, 2.3)); // Prints `{Meow; 1; 2.3}`
c++20 makes everything very easy:
void print_tuple(auto&& t) noexcept
{
[&]<auto ...I>(std::index_sequence<I...>) noexcept
{
(
[&]() noexcept
{
if constexpr(I)
{
std::cout << ", ";
}
std::cout << std::get<I>(t);
}(),
...
);
std::cout << '\n';
}
(
std::make_index_sequence<
std::tuple_size_v<std::remove_cvref_t<decltype(t)>>
>()
);
}
I have a syntax problem with expanding a tuple to its content.
The working code I have:
class Example
{
public:
static void Go( int i, float f)
{
std::cout << "p1: " << i << std::endl;
std::cout << "p2: " << f << std::endl;
}
template <typename T, size_t ... I>
static void Do( T parm )
{
Go( std::get<I>( parm)...);
}
};
int main()
{
using X = std::tuple<int, float>;
Example::Do<X,0,1>( std::make_tuple( 1,2.2)) ;
}
But I want to call the expansion with something like
int main()
{
using X = std::tuple<int, float>;
using IDX = std::std::index_sequence_for<int, float>;
Example::Do<X,IDX>( std::make_tuple( 1,2.2)) ;
}
So I am searching for something like ( which can not compiled... ):
template <typename T, size_t ... I>
static void Do<T, std::index_sequence<I...>>(T parm)
{
Go( std::get<I>( parm)...);
}
Pass the index sequence by value:
template <typename T, size_t... I>
static void Do(T parm, std::index_sequence<I...>)
{
Go(std::get<I>(parm)...);
}
Call the method like this:
Example::Do(std::make_tuple(1, 2.2),
std::index_sequence_for<int, float>{});
The problem is that your std::index_sequence (IDX) is not expanding in the template parameters to what you need:
Example::Do<X, IDX>(std::make_tuple(1, 2.2));
...will not "expand" to:
Example::Do<X, 0, 1>(std::make_tuple(1, 2.2)); // This works
What you need is to let the compiler deduce the template arguments for ...I, to do so change your static method to:
template <typename T, size_t ... I>
static void Do(T parm, std::index_sequence<I...>)
{
Go(std::get<I>(parm)...);
}
And then call it with:
Example::Do(std::make_tuple(1, 2.2), IDX{});
The compiler will automatically deduce the template arguments and call Example::Do<X, 0, 1> as needed.
If you want to be able to call Do without the second arguments, you can add another layer of abstraction in Example:
class Example
{
public:
static void Go(int i, float f) {
std::cout << "p1: " << i << std::endl;
std::cout << "p2: " << f << std::endl;
}
template <typename Tuple>
static void Do(Tuple &&parm) {
_Do(std::forward<Tuple>(parm),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>{});
}
private:
template <typename Tuple, size_t... I>
static void _Do(Tuple &&parm, std::index_sequence<I...>) {
Go(std::get<I>(std::forward<Tuple>(parm))...);
}
};
Then:
Example::Do(std::make_tuple(1, 2.2));
Note that the three versions:
Example::Do<X, 0, 1> (std::make_tuple(1, 2.2));
Example::Do(std::make_tuple(1, 2.2), IDX{});
Example::Do(std::make_tuple(1, 2.2));
Will likely results in the same code after compiler optimization (on my machine with clang++-3.7 and -O1, the three assembly files are strictly identical).