C++11 Way to Index Tuple at Runtime Without Using Switch

C++11 way to index tuple at runtime without using switch

Here's a version that doesn't use an index sequence:

template <size_t I>
struct visit_impl
{
template <typename T, typename F>
static void visit(T& tup, size_t idx, F fun)
{
if (idx == I - 1) fun(std::get<I - 1>(tup));
else visit_impl<I - 1>::visit(tup, idx, fun);
}
};

template <>
struct visit_impl<0>
{
template <typename T, typename F>
static void visit(T& tup, size_t idx, F fun) { assert(false); }
};

template <typename F, typename... Ts>
void visit_at(std::tuple<Ts...> const& tup, size_t idx, F fun)
{
visit_impl<sizeof...(Ts)>::visit(tup, idx, fun);
}

template <typename F, typename... Ts>
void visit_at(std::tuple<Ts...>& tup, size_t idx, F fun)
{
visit_impl<sizeof...(Ts)>::visit(tup, idx, fun);
}

DEMO

Run-time indexing of tuple

This can probably be done more nicely, but here is an attempt according to your requirements in the comments.

Requires C++17, works on Clang, but gives an Internal Compiler Error on GCC.

It does require though, that you make the constructing function SFINAE-friendly, otherwise there is no way of checking whether it can be called:

So use

return [](auto... args) -> decltype(U(args)...) { return U(args...); };

instead of

return [](auto... args) { return U(args...); };

The behavior of this function given arguments tup and index is as follows:

It returns a lambda that when called with a list of arguments will return a std::variant of all the types that could result from calls of the form std::get<i>(tup)(/*arguments*/). Which one of these is actually called and stored in the returned variant is decided at runtime through the index argument. If index refers to a tuple element that cannot be called as if by std::get<index>(tup)(/*arguments*/), then an exception is thrown at runtime.

The intermediate lambda can be stored and called later. Note however that it saves a reference to the tup argument, so you need to make sure that the argument out-lives the lambda if you don't call and discard it immediately.

#include <tuple>
#include <type_traits>
#include <variant>
#include <utility>
#include <stdexcept>

template<auto V> struct constant_t {
static constexpr auto value = V;
using value_type = decltype(value);
constexpr operator value_type() const {
return V;
}
};

template<auto V>
inline constexpr auto constant = constant_t<V>{};

template<auto V1, auto V2>
constexpr auto operator+(constant_t<V1>, constant_t<V2>) {
return constant<V1+V2>;
}

template<typename T>
struct wrap_t {
using type = T;
constexpr auto operator+() const {
return static_cast<wrap_t*>(nullptr);
}
};

template<typename T>
inline constexpr auto wrap = wrap_t<T>{};

template<auto A>
using unwrap = typename std::remove_pointer_t<decltype(A)>::type;

template <typename Tup>
auto magic_get(Tup&& tup, std::size_t index) {
return [&tup, index](auto&&... args) {
// Get the input tuple size
constexpr auto size = std::tuple_size_v<std::remove_const_t<std::remove_reference_t<Tup>>>;

// Lambda: check if element i of tuple is invocable with given args
constexpr auto is_valid = [](auto i) {
return std::is_invocable_v<decltype(std::get<i>(tup)), decltype(args)...>;
};

// Lambda: get the wrapped return type of the invocable element i of tuple with given args
constexpr auto result_type = [](auto i) {
return wrap<std::invoke_result_t<decltype(std::get<i>(tup)), decltype(args)...>>;
};

// Recursive lambda call: get a tuple of wrapped return type using `result_type` lambda
constexpr auto valid_tuple = [=]() {
constexpr auto lambda = [=](auto&& self, auto i) {
if constexpr (i == size)
return std::make_tuple();
else if constexpr (is_valid(i))
return std::tuple_cat(std::make_tuple(result_type(i)), self(self, i + constant<1>));
else
return self(self, i + constant<1>);
};
return lambda(lambda, constant<std::size_t{0}>);
}();

// Lambda: get the underlying return types as wrapped variant
constexpr auto var_type =
std::apply([](auto... args) { return wrap<std::variant<unwrap<+args>...>>; }, valid_tuple);

/**
* Recursive lambda: get a variant of all underlying return type of matched functions, which
* contains the return value of calling function with given index and args.
*
* @param self The lambda itself
* @param tup A tuple of functions
* @param index The index to choose from matched (via args) functions
* @param i The running index to reach `index`
* @param j The in_place_index for constructing in variant
* @param args The variadic args for callling the function
* @return A variant of all underlying return types of matched functions
*/
constexpr auto lambda = [=](auto&& self, auto&& tup, std::size_t index, auto i, auto j,
auto&&... args) -> unwrap<+var_type> {
if constexpr (i == size)
throw std::invalid_argument("index too large");
else if (i == index) {
if constexpr (is_valid(i)) {
return unwrap<+var_type>{std::in_place_index<j>,
std::get<i>(tup)(decltype(args)(args)...)};
} else {
throw std::invalid_argument("invalid index");
}
} else {
return self(self, decltype(tup)(tup), index, i + constant<1>, j + constant<is_valid(i)>,
decltype(args)(args)...);
}
};
return lambda(lambda, std::forward<Tup>(tup), index, constant<std::size_t{0}>,
constant<std::size_t{0}>, decltype(args)(args)...);
};
}

In C++20, you can simplify this by

  • using std::remove_cvref_t<Tup> instead of std::remove_const_t<std::remove_reference_t<Tup>>

  • changing the definition of unwrap to:

    template<auto A>
    using unwrap = typename decltype(A)::type;

    and using it as unwrap<...> instead of unwrap<+...>, which also allows removing the operator+ from wrap_t.


The purpose of wrap/unwrap:

wrap_t is meant to turn a type into a value that I can pass into functions and return from them without creating an object of the original type (which could cause all kinds of issues). It is really just an empty struct templated on the type and a type alias type which gives back the type.

I wrote wrap as a global inline variable, so that I can write wrap<int> instead of wrap<int>{}, since I consider the additional braces annoying.

unwrap<...> isn't really needed. typename decltype(...)::type does the same, it just gives back the type that an instance of wrap represents.

But again I wanted some easier way of writing it, but without C++20 this is not really possible in a nice way. In C++20 I can just pass the wrap object directly as template argument, but that doesn't work in C++17.

So in C++17 I "decay" the object to a pointer, which can be a non-type template argument, with an overloaded operator+, mimicking the syntax of the common lambda-to-function-pointer trick using the unary + operator (but I could have used any other unary operator).

The actual pointer value doesn't matter, I only need the type, but the template argument must be a constant expression, so I let it be a null pointer. The latter requirement is why I am not using the built-in address-of operator & instead of an overloaded +.

How to get the i-th element from an std::tuple when i isn't know at compile-time?

You cannot. That's not what a tuple is for. If you need dynamic access to an element, use std::array<T,N>, which is almost identical to std::tuple<T,...,T> but gives you the dynamic [i]-operator; or even a fully dynamic container like std::vector<T>.

Optimal way to access std::tuple element in runtime by index

Assuming you pass something similar to a generic lambda, i.e. a function object with an overloaded function call operator:

#include <iostream>

struct Func
{
template<class T>
void operator()(T p)
{
std::cout << __PRETTY_FUNCTION__ << " : " << p << "\n";
}
};

The you can build an array of function pointers:

#include <tuple>

template<int... Is> struct seq {};
template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {};

template<int N, class T, class F>
void apply_one(T& p, F func)
{
func( std::get<N>(p) );
}

template<class T, class F, int... Is>
void apply(T& p, int index, F func, seq<Is...>)
{
using FT = void(T&, F);
static constexpr FT* arr[] = { &apply_one<Is, T, F>... };
arr[index](p, func);
}

template<class T, class F>
void apply(T& p, int index, F func)
{
apply(p, index, func, gen_seq<std::tuple_size<T>::value>{});
}

Usage example:

int main()
{
std::tuple<int, double, char, double> t{1, 2.3, 4, 5.6};
for(int i = 0; i < 4; ++i) apply(t, i, Func{});
}

clang++ also accepts an expansion applied to a pattern that contains a lambda expression:

static FT* arr[] = { [](T& p, F func){ func(std::get<Is>(p)); }... };

(although I've to admit that looks really weird)

g++4.8.1 rejects this.

Access tuple element in C++11 via compile time variable in function

You could use a std::array instead of a std::tuple. In the example given, the members of the tuple all have the same type.

So, we could do:

char get_elem_i(int i, std::array<char, 2> t)
{
return t[i];
}

Here's a slight variant on the example you gave to show why it's not directly possible in the general case:

???? get_elem_i(int i, std::tuple<char, struct foo, class bar> t) {
return std::get<i>(t);
}

What is the return type of that function? char? struct foo?


And you could always write a function like this:

char get_elem_i(int i, std::tuple<char, char> t) {
switch (i) {
case 0: return std::get<0>(t);
case 1: return std::get<1>(t);
}

assert(false);
}

How to extract all tuple elements of given type(s) into new tuple

Only C++17 is needed here.

std::tuple_cat is one of my favorite tools.

  1. Use a std::index_sequence to chew through the tuple

  2. Use a specialization to pick up either a std::tuple<> or a std::tuple<T> out of the original tuple, for each indexed element.

  3. Use std::tuple_cat to glue everything together.

  4. The only tricky part is checking if each tuple element is wanted. To do that, put all the wanted types into its own std::tuple, and use a helper class for that part, too.

#include <utility>
#include <tuple>
#include <iostream>

// Answer one simple question: here's a type, and a tuple. Tell me
// if the type is one of the tuples types. If so, I want it.

template<typename wanted_type, typename T> struct is_wanted_type;

template<typename wanted_type, typename ...Types>
struct is_wanted_type<wanted_type, std::tuple<Types...>> {

static constexpr bool wanted=(std::is_same_v<wanted_type, Types>
|| ...);
};

// Ok, the ith index in the tuple, here's its std::tuple_element type.
// And wanted_element_t is a tuple of all types we want to extract.
//
// Based on which way the wind blows we'll produce either a std::tuple<>
// or a std::tuple<tuple_element_t>.

template<size_t i, typename tuple_element_t,
typename wanted_element_t,
bool wanted=is_wanted_type<tuple_element_t, wanted_element_t>::wanted>
struct extract_type {

template<typename tuple_type>
static auto do_extract_type(const tuple_type &t)
{
return std::tuple<>{};
}
};

template<size_t i, typename tuple_element_t, typename wanted_element_t>
struct extract_type<i, tuple_element_t, wanted_element_t, true> {

template<typename tuple_type>
static auto do_extract_type(const tuple_type &t)
{
return std::tuple<tuple_element_t>{std::get<i>(t)};
}
};

// And now, a simple fold expression to pull out all wanted types
// and tuple-cat them together.

template<typename wanted_element_t, typename tuple_type, size_t ...i>
auto get_type_t(const tuple_type &t, std::index_sequence<i...>)
{
return std::tuple_cat( extract_type<i,
typename std::tuple_element<i, tuple_type>::type,
wanted_element_t>::do_extract_type(t)... );
}

template<typename ...wanted_element_t, typename ...types>
auto get_type(const std::tuple<types...> &t)
{
return get_type_t<std::tuple<wanted_element_t...>>(
t, std::make_index_sequence<sizeof...(types)>());
}

int main()
{
std::tuple<int, const char *, double> t{1, "alpha", 2.5};

std::tuple<double, int> u=get_type<int, double>(t);

std::cout << std::get<0>(u) << " " << std::get<1>(u) << std::endl;

std::tuple<int, int, int, char, char, char, double, double, float> tt;

auto uu=get_type<float, double>(tt);

static_assert(std::is_same_v<decltype(uu),
std::tuple<double, double, float>>);

return 0;
}


Related Topics



Leave a reply



Submit