Replace N-th element of a std::tuple - c++

What is the shortest / best way to replace the n-th element of a tuple with a value (which may or may not have a different type)? Solutions including c++20 are fine.
[EDIT: I would prefer something not requiring other libraries, but I'm still interested what solutions are possible with e.g. boost].
I.e.:
#include <cassert>
#include <tuple>
template<std::size_t N, ... >
auto replace_tuple_element( ... ) // <- Looking for a suitable implementation
struct Foo {
int value;
};
int main()
{
auto t1 = std::tuple{ 0, 1, 2, 3 };
auto t2 = replace_tuple_element<2>( t1, Foo{10} );
assert( std::get<0>(t2) == std::get<0>(t1));
assert( std::get<1>(t2) == std::get<1>(t1));
assert( std::get<2>(t2).value == 10);
assert( std::get<3>(t2) == std::get<3>(t1));
}
Note: Just replacing the n-th type in a typelist has e.g. be discussed here: How do I replace a tuple element at compile time?.
But I also want to replace the value and hope that there are simpler/more elegant solutions now in c++20 than back when that question was asked.

One solution I found for c++20 is this:
#include <cassert>
#include <tuple>
#include <type_traits>
template<std::size_t N, class TupleT, class NewT>
constexpr auto replace_tuple_element( const TupleT& t, const NewT& n )
{
constexpr auto tail_size = std::tuple_size<TupleT>::value - N - 1;
return [&]<std::size_t... I_head, std::size_t... I_tail>
( std::index_sequence<I_head...>, std::index_sequence<I_tail...> )
{
return std::tuple{
std::get<I_head>( t )...,
n,
std::get<I_tail + N + 1>( t )...
};
}(
std::make_index_sequence<N>{},
std::make_index_sequence<tail_size>{}
);
}
struct Foo {
int value;
};
int main()
{
auto t1 = std::tuple{ 0, 1, 2, 3 };
auto t2 = replace_tuple_element<2>( t1, Foo{10} );
assert( std::get<0>(t2) == std::get<0>(t1));
assert( std::get<1>(t2) == std::get<1>(t1));
assert( std::get<2>(t2).value == 10);
assert( std::get<3>(t2) == std::get<3>(t1));
}
What I like about the solution is that it is a single, self containied function. I wonder if there is something even shorter and/or more readable though.

Possible solution:
template<std::size_t i>
using index = std::integral_constant<std::size_t, i>;
template<std::size_t N, class Tuple, typename S>
auto replace_tuple_element(Tuple&& tuple, S&& s) {
auto get_element = [&tuple, &s]<std::size_t i>(Index<i>) {
if constexpr (i == N)
return std::forward<S>(s);
else
return std::get<i>(std::forward<Tuple>(tuple));
};
using T = std::remove_reference_t<Tuple>;
return [&get_element]<std::size_t... is>(std::index_sequence<is...>) {
return std::make_tuple(get_element(index<is>{})...);
}(std::make_index_sequence<std::tuple_size_v<T>>{});
}
Note this decays all element types, removing references and const.
This amendment partially addresses this issue:
template<std::size_t N, class Tuple, typename S>
auto replace_tuple_element(Tuple&& tuple, S&& s) {
using T = std::remove_reference_t<Tuple>;
auto get_element = [&tuple, &s]<std::size_t i>(index<i>) {
if constexpr (i == N)
return std::forward<S>(s);
else
if constexpr (std::is_lvalue_reference_v<std::tuple_element_t<i, T>>)
return std::ref(std::get<i>(std::forward<Tuple>(tuple)));
else
return std::get<i>(std::forward<Tuple>(tuple));
};
return [&get_element]<std::size_t... is>(std::index_sequence<is...>) {
return std::make_tuple(get_element(index<is>{})...);
}(std::make_index_sequence<std::tuple_size_v<T>>{});
}
Now replace_tuple_element also follows the convention of std::make_tuple that converts std::reference_wrapper arguments into references. It does preserve reference types, but drops top-level constness.
struct Foo {
Foo(int i) : value(i) {}
int value;
};
int main() {
int i = 1;
int j = 2;
auto t1 = std::make_tuple(std::make_unique<Foo>(0), std::ref(i), std::cref(j), 4);
static_assert(std::is_same_v<decltype(t1),
std::tuple<std::unique_ptr<Foo>, int&, const int&, int>>);
auto t2 = replace_tuple_element<1>(std::move(t1), std::make_unique<Foo>(5));
static_assert(std::is_same_v<decltype(t2),
std::tuple<std::unique_ptr<Foo>, std::unique_ptr<Foo>, const int&, int>>);
auto t3 = replace_tuple_element<0>(std::move(t2), std::cref(i));
static_assert(std::is_same_v<decltype(t3),
std::tuple<const int&, std::unique_ptr<Foo>, const int&, int>>);
auto t4 = replace_tuple_element<2>(std::move(t3), i);
static_assert(std::is_same_v<decltype(t4),
std::tuple<const int&, std::unique_ptr<Foo>, int, int>>);
}
Full demo with run-time asserts

This should do it:
template<std::size_t N, class U, class T>
auto replace_tuple_element(T&& t, U&& u) {
return [&]<std::size_t... I>(std::index_sequence<I...>) {
return std::tuple<std::conditional_t<I == N, U, std::tuple_element_t<I, std::decay_t<T>>>...>{
[&]() -> decltype(auto) {
if constexpr (I == N) return std::forward<U>(u);
else return static_cast<std::tuple_element_t<I, std::decay_t<T>>>(std::get<I>(t));
}()...};
}(std::make_index_sequence<std::tuple_size_v<std::decay_t<T>>>{});
}
You can remove some of the casts, forwards etc. if you're only concerned with value semantics.
The only thing new here is lambda template parameters to infer the indexing argument.

If we want to both preserve all the types exactly as they are, and also do the same kind of reference unwrapping thing that the standard library typically does, then we need to make a small change to what the other implementations are here.
unwrap_ref_decay will do a decay_t on the type, and then turn reference_wrapper<T> into T&. And using Boost.Mp11 for a few things that just make everything nicer:
template <size_t N, typename OldTuple, typename NewType>
constexpr auto replace_tuple_element(OldTuple&& tuple, NewType&& elem)
{
using Old = std::remove_cvref_t<OldTuple>;
using R = mp_replace_at_c<Old, N, std::unwrap_ref_decay_t<NewType>>;
static constexpr auto Size = mp_size<Old>::value;
auto get_nth = [&](auto I) -> decltype(auto) {
if constexpr (I == N) return std::forward<NewType>(elem);
else return std::get<I>(std::forward<OldTuple>(tuple));
};
return [&]<size_t... Is>(std::index_sequence<Is...>) {
return R(get_nth(mp_size_t<Is>())...);
}(std::make_index_sequence<Size>());
}
This implementation means that given:
std::tuple<int const, int const> x(1, 2);
int i = 42;
auto y = replace_tuple_element<1>(x, std::ref(i));
y is a tuple<int const, int&>.

This is a good use case for a counterpart of tuple_cat that, instead of concatenating tuples, gives you a slices of a tuple. Unfortunately, this doesn't exist in the standard library, so we'll have to write it ourselves:
template <std::size_t Begin, std::size_t End, typename Tuple>
constexpr auto tuple_slice(Tuple&& t)
{
return [&]<std::size_t... Ids> (std::index_sequence<Ids...>)
{
return std::tuple<std::tuple_element_t<Ids, std::remove_reference_t<Tuple>>...>
{std::get<Begin + Ids>(std::forward<Tuple>(t))...};
} (std::make_index_sequence<End - Begin>{});
}
Just like tuple_cat, this preserve the exact same types of the original tuple.
With tuple_cat and tuple_slice, the implementation of replace_tuple_element feels quite elegant:
template <std::size_t N, typename Tuple, typename T>
constexpr auto replace_tuple_element(Tuple&& tuple, T&& t)
{
constexpr auto Size = std::tuple_size_v<std::remove_reference_t<Tuple>>;
return std::tuple_cat(
tuple_slice<0, N>(std::forward<Tuple>(tuple)),
std::make_tuple(std::forward<T>(t)),
tuple_slice<N + 1, Size>(std::forward<Tuple>(tuple))
);
}
Using make_tuple preserves the behavior of turning reference_wrapper<T> into T&. Demo

Related

Find first element in tuple based on parameterized primitive

I have a contrived example of what I would like to achieve.
#include <tuple>
#include <array>
template <int Val>
struct Value
{
static constexpr auto aValue = Val;
double arbitrary_operation() { return aValue * 2.0; };
};
struct Meta
{
auto do_calculation(std::floating_point auto x) { return x * 4.0; }
};
constexpr auto Values = std::tuple{Value<5>{}, Value<6>{}, Value<7>{}};
constexpr auto ValueMetadata = std::array<std::pair<int, Meta>, 3>{{{5, Meta{}}, {6, Meta{}}, {7, Meta{}}}};
void do_work()
{
std::array<double, ValueMetadata.size()> results;
for (auto& m : ValueMetadata)
{
// find first element in Values tuple that corresponds to parameterized integer (m.first == aValue)
auto value = get_first_element_in_values_tuple<m.first>(); // TODO
// perform arbitrary_operation on that Value and aggregate results
auto arbitrary_value = value.arbitrary_operation();
auto result = m.second.do_calculation(arbitrary_value);
// store in results array
}
}
The body of do_work is presenting some difficult - and I'm wondering where this can be performed at compile-time.
So I'm not sure what the templated Value class brings you. Why not make them non-template, then you can just make Values an std::array, and use constexpr algorithms from C++20?
Either way, if you insist on the tuple solution, here's one way you can create a tuple_find_if function with the help of C++20 constexpr algorithms and std::index_sequence:
template<typename Tuple, typename Func, std::size_t ... N>
consteval auto tuple_find_if_impl(Tuple tup, Func func, std::index_sequence<N...>)
{
constexpr auto compares = std::array{func(std::get<N>(tup))...};
constexpr auto index = std::ranges::find(compares, true) - compares.begin();
return std::get<index>(tup);
}
template<typename ... Ts, typename Func>
consteval auto tuple_find_if(std::tuple<Ts...> tup, Func func)
{
return tuple_find_if_impl(tup, func, std::make_index_sequence<sizeof...(Ts)>{});
}
Demo: https://godbolt.org/z/cEfPoPbdc
And here's a recursive version that works with C++17:
template<typename T, typename ... Ts, typename Func, std::size_t ... N>
constexpr auto tuple_find_if_impl(std::tuple<T, Ts...> tup, Func func, std::index_sequence<N...>)
{
if constexpr(func(std::get<0>(tup)))
{
return std::get<0>(tup);
}
else
{
return tuple_find_if_impl(std::make_tuple(std::get<N + 1>(tup)...), func, std::make_index_sequence<sizeof...(Ts) - 1>{});
}
}
template<typename ... Ts, typename Func>
constexpr auto tuple_find_if(std::tuple<Ts...> tup, Func func)
{
return tuple_find_if_impl(tup, func, std::make_index_sequence<sizeof...(Ts) - 1>{});
}
Demo: https://godbolt.org/z/sc5Txronh
You can first convert the tuple to an array and use std::find to find the appropriate index, then use variant to type-erasure the index.
#include <algorithm>
#include <variant>
template<std::size_t N>
constexpr auto VarIndexArray = []<std::size_t... Is>(std::index_sequence<Is...>) {
using VarType = std::variant<std::integral_constant<std::size_t, Is>...>;
return std::array{VarType{std::in_place_index<Is>}...};
}(std::make_index_sequence<N>{});
void do_work() {
std::array<double, ValueMetadata.size()> results;
constexpr auto ValueArray = std::apply(
[](auto... vals) { return std::array{decltype(vals)::aValue...}; }, Values);
constexpr auto IndexArray = VarIndexArray<std::tuple_size_v<decltype(Values)>>;
for (auto& m : ValueMetadata) {
// find first element in Values tuple that corresponds to parameterized integer
auto index = std::ranges::find(ValueArray, m.first) - ValueArray.begin();
auto result = std::visit(
[&m](auto index) {
auto arbitrary_value = std::get<index>(Values).arbitrary_operation();
auto result = m.second.do_calculation(arbitrary_value);
return result;
}, IndexArray[index]);
}
}
Demo
I may be misinterpreting your constraints, but if I understand correctly, you want to find the matching tuple element for each pair in your array, then aggregate the results. Depending on exactly what you want there's certainly better solutions using fold expressions and index sequences, but I believe I have something that works and produces a compile time result:
live example
template <int I, std::size_t TupleIndex>
consteval double find_match() {
// Compare I to the current aValue, if they match, return the val.
if constexpr (I == std::get<TupleIndex>(Values).aValue) {
return std::get<TupleIndex>(Values).val();
} else {
// Otherwise, move onto the next element of the tuple.
return find_match<I, TupleIndex+1>();
}
}
template <std::size_t I>
consteval double accumulate_matches() {
if constexpr (I == ValueMetadata.size()) {
return 0;
} else {
// Change this expression to accumulate with something
// other than a simple summation.
// 0 to start searching at the beginning of the tuple.
// +1 in the recursive call to get to the next element of ValueMetadata
return find_match<ValueMetadata[I].first, 0>()
+ accumulate_matches<I+1>();
}
}
consteval double get_total() {
return accumulate_matches<0>();
}

Improving fold function

I have implemented a simple fold function in C++ that accepts a lambda, and can fold multiple vectors at the same time at compile time. I am wondering if it could be simplified in some manner (I have provided both a recursive version and an iteratively recursive version - I am unsure which should have better performance): https://godbolt.org/z/39pW81
Performance optimizations are also welcome - in that regard is any of the two approaches faster?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
and can fold multiple vectors at the same time at compile time
Not exactly: if you want to operate compile-time
(1) you have to define constexpr the tvecn constructor and
(2) you have to define constexpr the foldhelper function and
(3) you have to declare constexpr a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) you have to place the result of fold in a constexpr variable (or, more generally speaking, in a place where the value is required compile time, as the size field of a C-style array, or a template value parameter, or a static_assert() test)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
Sure.
First of all: if you can, avoid to reinventing the weel: your tvecn is a simplified version of std::array.
Suggestion: use std::array (if you can obviously)
Second: you tagged C++17 so you can use folding
Suggestion: use it also for all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
More in general: when you have to define a custom type traits that has to provide a number, inherit (if possible) from std::integral_constant (or std::bool_constant, or std::true_type, or std::false_type: all std::integral_constant specializations). So you automatically inherit all std::integral_constant facilities.
Third: almost all C++ standard uses std::size_t, not int, for sizes.
Suggestion: when you have to do with sizes, use std::size_t, not int. This way you can avoid a lot of annoying troubles.
Fourth: from main() you should return only EXIT_SUCCESS (usually zero) or EXIT_FAILURE (usually 1)
Suggestion: avoid things as
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
Fifth: never underestimate the power of the comma operator.
Suggestion: avoid recursion at all and use template folding also for the helper function; by example
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
that you can call as follows from fold()
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
The following is a full compiling, and simplified, example
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}
With Fold expression, it might be:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
Demo

How to iterate over the types of std::variant?

I have some variant using V = std::variant<A, B, C> and a function with the prototype V parse(const json&). The function should try to parse all the types (e.g. A, B, then C) till the first success (and it should do it implicitly, for there will be many types in time).
How to implement something of this kind?
We may use std::variant_size somehow.
Here is something close to what I need.
My solution is to list parsers of all the types explicitly.
V parse(const json& i_j)
{
using Parser = std::function<MaybeV(const json&)>;
static const auto ps = std::vector<Parser>{
[](const auto& j)->MaybeV{return j.get<std::optional<A>>;},
[](const auto& j)->MaybeV{return j.get<std::optional<B>>;},
[](const auto& j)->MaybeV{return j.get<std::optional<C>>;}
};
for (const auto& p : ps)
if (auto opt_result = p(i_j))
return std::move(*opt_result);
throw ParseError("Can't parse");
}
Yet it may definitely be simplified, for the lambdas different only in type and what I actually need is to iterate over the types of std::variant.
You want compile time integers from 0 to variant's size minus 1, and possibly early exit from iterating over them.
There are lots of ways to get compile time integers. Two of my favorites are generating a tuple of integral constants, or calling a continuation with a parameter pack of integral constants.
Taking the tuple of integral constants version, you can use a "tuple for each" to visit each in turn.
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<std::size_t...Is>
constexpr std::tuple< index_t<Is>... > make_indexes(std::index_sequence<Is...>){
return std::make_tuple(index<Is>...);
}
template<std::size_t N>
constexpr auto indexing_tuple = make_indexes(std::make_index_sequence<N>{});
from variant size you call that. From that you call a tuple_foreach.
The tuple_foreach emplaces the optional return value if parsing succeeds and it wasn't already parsed.
V parse(const json& j)
{
auto indexes = indexing_tuple<tuple_size_v<V>>;
std::optional<V> retval;
tuple_foreach(indexes, [&](auto I){ // I is compile time integer
if(retval) return;
auto p = j.get<tuple_alternative_t<I>>();
if(p) retval.emplace(std::move(*p));
});
if(!retval) throw ParseError("Can't parse");
return std::move(*retval);
}
tuple_foreach can be found on the internet, but for completeness:
template<std::size_t...Is, class T, class F>
auto tuple_foreach( std::index_sequence<Is...>, T&& tup, F&& f ) {
( f( std::get<Is>( std::forward<T>(tup) ) ), ... );
}
template<class T, class F>
auto tuple_foreach( T&& tup, F&& f ) {
auto indexes = std::make_index_sequence< std::tuple_size_v< std::decay_t<T> > >{};
return tuple_foreach( indexes, std::forward<T>(tup), std::forward<F>(f) );
}
which should do it in c++17.
Types can be processed recursively from 0 to std::variant_size_v (exclusive), with an if-constexpr limiting template instantiations:
#include <variant>
#include <optional>
#include <cstddef>
#include <utility>
using V = std::variant<A, B, C>;
template <std::size_t I = 0>
V parse(const json& j)
{
if constexpr (I < std::variant_size_v<V>)
{
auto result = j.get<std::optional<std::variant_alternative_t<I, V>>>();
return result ? std::move(*result) : parse<I + 1>(j);
}
throw ParseError("Can't parse");
}
DEMO

Get a std::tuple element as std::variant

Given a variant type:
using Variant = std::variant<bool, char, int, float, double, std::string>;
and a tuple type containing elements restricted to this variant types (duplicates and omissions are possible, but no additional types):
using Tuple = std::tuple<char, int, int, double, std::string>;
How to implement methods that gets and sets a tuple element by a given index as Variant at runtime:
Variant Get(const Tuple & val, size_t index);
void Set(Tuple & val, size_t index, const Variant & elem_v);
I have two implementations in my code, but I have an impression that there can be a better one. My first implementation uses std::function and the second builds an array of some Accessor pointers that imposes restrictions on moving and copying my object (because its address changes). I wonder if someone knows the right way to implement this.
EDIT1:
The following example probably clarifies what I mean:
Tuple t = std::make_tuple(1, 2, 3, 5.0 "abc");
Variant v = Get(t, 1);
assert(std::get<int>(v) == 2);
Set(t, 5, Variant("xyz"));
assert(std::get<5>(t) == std::string("xyz"));
I'm going to continue my theme of recommending Boost.Mp11 for all metaprogramming things, because there is always a function for that. In this case, we want mp_with_index. That function lifts a runtime index into a compile-time index.
Variant Get(Tuple const& val, size_t index)
{
return mp_with_index<std::tuple_size_v<Tuple>>(
index,
[&](auto I){ return Variant(std::get<I>(val)); }
);
}
Given that in the OP, the indices of the Tuple and the Variant don't even line up, the Set needs to actually visit the Variant rather than relying on the index. I'm using is_assignable here as the constraint, but that can be adjusted to as fitting for the problem (e.g. maybe it should be is_same).
void Set(Tuple& val, size_t index, Variant const& elem_v)
{
mp_with_index<std::tuple_size_v<Tuple>>(
index,
[&](auto I){
std::visit([&](auto const& alt){
if constexpr (std::is_assignable_v<
std::tuple_element_t<Tuple, I>,
decltype(alt)>)
{
std::get<I>(val) = alt;
} else {
throw /* something */;
}
}, elem_v);
});
}
If you require that every type in the Tuple appears exactly once in the Variant, and you want to directly only assign from that type without doing any conversions, this can be simplified to:
void Set(Tuple& val, size_t index, Variant const& elem_v)
{
mp_with_index<std::tuple_size_v<Tuple>>(
index,
[&](auto I){
using T = std::tuple_element_t<Tuple, I>;
std::get<I>(val) = std::get<T>(elem_v);
});
}
which will throw if the variant is not engaged with that type.
Here are possible implementations of a get_runtime and set_runtime functions that rely on recursion to try to match the runtime index to a compile time one:
template <class Variant, class Tuple, std::size_t Index = 0>
Variant get_runtime(Tuple &&tuple, std::size_t index) {
if constexpr (Index == std::tuple_size_v<std::decay_t<Tuple>>) {
throw "Index out of range for tuple";
}
else {
if (index == Index) {
return Variant{std::get<Index>(tuple)};
}
return get_runtime<Variant, Tuple, Index + 1>(
std::forward<Tuple>(tuple), index);
}
}
template <class Tuple, class Variant, std::size_t Index = 0>
void set_runtime(Tuple &tuple, std::size_t index, Variant const& variant) {
if constexpr (Index == std::tuple_size_v<std::decay_t<Tuple>>) {
throw "Index out of range for tuple";
}
else {
if (index == Index) {
// Note: You should check here that variant holds the correct type
// before assigning.
std::get<Index>(tuple) =
std::get<std::tuple_element_t<Index, Tuple>>(variant);
}
else {
set_runtime<Tuple, Variant, Index + 1>(tuple, index, variant);
}
}
}
You can use them like your Get and Set:
using Variant = std::variant<bool, char, int, float, double, std::string>;
using Tuple = std::tuple<char, int, int, double, std::string>;
Tuple t = std::make_tuple(1, 2, 3, 5.0, "abc");
Variant v = get_runtime<Variant>(t, 1);
assert(std::get<int>(v) == 2);
set_runtime(t, 4, Variant("xyz"));
assert(std::get<4>(t) == std::string("xyz"));
template <size_t... I>
Variant GetHelper(const Tuple& val, size_t index, std::index_sequence<I...>)
{
Variant value;
int temp[] = {
([&]
{
if (index == I)
value = std::get<I>(val);
}(), 0)... };
return value;
}
Variant Get(const Tuple& val, size_t index)
{
return GetHelper(val, index, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
template <size_t... I>
void SetHelper(Tuple& val, size_t index, Variant elem_v, std::index_sequence<I...>)
{
int temp[] = {
([&]
{
using type = std::tuple_element_t<I, Tuple>;
if (index == I)
std::get<I>(val) = std::get<type>(elem_v);
}(), 0)... };
}
void Set(Tuple& val, size_t index, Variant elem_v)
{
SetHelper(val, index, elem_v, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
Explanation:
Use std::index_sequence to get access to every tuple element via compile time constant index I. Create a lambda for each index, that performs the desired action if the index matches, and call it immediately (note the () right after the lambdas). Use the syntax int temp[] = { (some_void_func(), 0)... } to actually call every lambda (you cannot use the unpacking syntax ... on void functions directly, hence this trick to assign it to an int array).
Alternatively, you can make your lambdas return some dummy int. Then you can call them via unpacking directly.
First, some machinery.
alternative is a variant of integral constants, which are stateless. We can then use visit on them to convert a bounded runtime value to a compile time value.
template<class T, T...Is>
using alternative = std::variant< std::integral_constant<T, Is>... >;
template<class List>
struct alternative_from_sequence;
template<class T, T...Is>
struct alternative_from_sequence< std::integer_sequence<T,Is...> > {
using type=alternative<T, Is...>;
};
template<class T, T Max>
using make_alternative = typename alternative_from_sequence<
std::make_integer_sequence<T, Max>
>::type;
template<class T, T Max, T Cur=Max-1>
make_alternative<T, Max> get_alternative( T value, std::integral_constant< T, Max > ={} ) {
if(Cur == 0 || value == Cur) {
return std::integral_constant<T, Cur>{};
}
if constexpr (Cur > 0)
{
return get_alternative<T, Max, Cur-1>( value );
}
}
template<class...Ts>
auto get_alternative( std::variant<Ts...> const& v ) {
return get_alternative<std::size_t, sizeof...(Ts) >( v.index() );
}
now your actual problem. This Get requires you pass your Variant type:
template<class Variant, class...Ts>
Variant Get(std::tuple<Ts...> const & val, size_t index) {
auto which = get_alternative<std::size_t, sizeof...(Ts)>( index );
return std::visit( [&val]( auto i )->Variant {
return std::get<i>(val);
}, which );
}
Your Set function seems toxic; if the types don't match, there is no practical recourse. I'll add a return value that states if the assignment failed:
template<class...Ts, class...Vs>
bool Set(
std::tuple<Ts...> & val,
std::size_t index,
const std::variant<Vs...>& elem_v
) {
auto tuple_which = get_alternative<std::size_t, sizeof...(Ts)>( index );
auto variant_which = get_alternative( elem_v );
return std::visit( [&val, &elem_v](auto tuple_i, auto variant_i) {
using variant_type = std::variant_alternative_t<variant_i, std::variant<Vs...>>;
using tuple_type = std::tuple_element_t< tuple_i, std::tuple<Ts...> >;
if constexpr (!std::is_assignable<tuple_type&, variant_type const&>{}) {
return false;
} else {
std::get<tuple_i>(val) = std::get<variant_i>(elem_v);
return true;
}
}, tuple_which, variant_which );
}
This Set returns false if the types are not assignable.
Live example.

How to sum over an array of variant using a visitor?

I'm trying to find a way to sum over an std::array of std::variant using a visitor. I've gotten this far, but I can't for the life of me figure out how to deduce the type of the visitors without including a void entry at the head of my visitor lambda list.
Does anyone know of a way I can deduce the return type of the lambdas in the visitor so that I don't have to rely on this?
Here's what I've got right now:
#include <array>
#include <iostream>
#include <string_view>
#include <type_traits>
#include <variant>
using namespace std::literals::string_view_literals;
template<typename... Base>
struct Visitor: Base ... {
using Base::operator()...;
};
template<typename... T>
Visitor(T...) -> Visitor<T...>;
// There has to be a better way to deduce Result than what I'm doing...
template<typename... T, typename S, typename... Ss, size_t N, typename Result = typename std::result_of_t<S()>>
constexpr std::enable_if_t<std::is_arithmetic_v<Result>, Result>
summation(const Visitor<S, Ss...> &visitor, const std::array<std::variant<T...>, N> &array) {
Result sum{};
for (const auto &a: array)
sum += std::visit(visitor, a);
return sum;
}
int main() {
constexpr Visitor visitor {
// This first entry should be unnecessary, I would think:
[]() -> double { return 0; },
[](double d) -> double { return d + 3.4; },
[](int i) -> double { return i - 2; },
[](std::string_view s) -> double { return s.size(); }
};
constexpr std::array<std::variant<int, double, std::string_view>, 5> arr{9.0, 9, 3, 5.2, "hello world"sv};
constexpr auto val = summation(visitor, arr);
std::cout << val << '\n';
}
Edit: I'd like the result to be constexpr.
Thanks for any help.
You are being way too explicit with your type infering when auto is there to let the compiler take care of that for you.
Once you are within the scope of the function decltype() and std::declval() make the inference (needed to create the default-initialized target) an easy matter since you can simply mock an actual invocation of the visitor.
template<typename... T, typename S, typename... Ss, size_t N>
constexpr auto summation(const Visitor<S, Ss...> &visitor, const std::array<std::variant<T...>, N> &array) {
using Result = decltype(std::visit(visitor, std::declval<std::variant<T...>>()));
static_assert(std::is_arithmetic_v<Result>);
Result sum{};
for (const auto &a: array)
sum += std::visit(visitor, a);
return sum;
}
I actually much prefer this style since an erroneous invocation will actually yield a sensible error message instead of "function not found". That is unless you will have non-arithmetic versions of accumulate() that you are trying to sfinae against (which would be weird).
A simplification (I hope) of the Frank's decltype()/std::declval() solution.
Using decltype()/std::declval(), you don't need to know S, Ss... and T...; you simply need a template type V for visitor and a template type for array.
You can also avoid the static_assert(), if you prefer, re-enabling SFINAE simply writing
template <typename V, typename A,
typename R = decltype(std::visit(std::declval<V>(), std::declval<A>().at(0)))>
constexpr std::enable_if_t<std::is_arithmetic_v<R>, R>
summation(V const & visitor, A const &array)
{
R sum{};
for (const auto &a: array)
sum += std::visit(visitor, a);
return sum;
}