Get a std::tuple element as std::variant - c++

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.

Related

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

The existing tuple overloads of std::get are limited to return exactly 1 element by index, or type. Imagine having a tuple with multiple elements of the same type and you want to extract all of them into a new tuple.
How to achieve a version of std::get<T> that returns a std::tuple of all occurrences of given type(s) like this?
template<typename... Ts_out>
constexpr std::tuple<Ts_out...> extract_from_tuple(auto& tuple) {
// fails in case of multiple occurences of a type in tuple
return std::tuple<Ts_out...> {std::get<Ts_out>(tuple)...};
}
auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
auto extract = extract_from_tuple <float, double>(tuple);
// expecting extract == std::tuple<float, double, double>{4.5f, 1.2, 2.3}
Not sure if std::make_index_sequence for accessing each element by std::get<index> and std::is_same_v per element could work.
Only C++17 is needed here.
std::tuple_cat is one of my favorite tools.
Use a std::index_sequence to chew through the tuple
Use a specialization to pick up either a std::tuple<> or a std::tuple<T> out of the original tuple, for each indexed element.
Use std::tuple_cat to glue everything together.
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;
}
With Boost.Mp11:
template <typename... Ts, typename Tuple>
auto extract_from_tuple(Tuple src) {
// the indices [0, 1, 2, ..., N-1]
using Indices = mp_iota<mp_size<Tuple>>;
// the predicate I -> Tuple[I]'s type is one of {Ts...}
using P = mp_bind<
mp_contains,
mp_list<Ts...>,
mp_bind<mp_at, Tuple, _1>>;
// the indices that satisfy P
using Chosen = mp_filter_q<P, Indices>;
// now gather all the appropriate elements
return [&]<class... I>(mp_list<I...>){
return std::tuple(std::get<I::value>(src)...);
}(Chosen{});
}
Demo.
If we want to use tuple_cat, a concise version of that:
template <typename... Ts, typename Tuple>
constexpr auto extract_from_tuple2(Tuple src) {
auto single_elem = []<class T>(T e){
if constexpr (mp_contains<mp_list<Ts...>, T>::value) {
return std::tuple<T>(e);
} else {
return std::tuple<>();
}
};
return std::apply([&](auto... e){
return std::tuple_cat(single_elem(e)...);
}, src);
}
Demo.
The idea of implementation is as follows (although boost.Mp11 may only need a few lines).
Take extract_from_tuple<float, double>, where tuple is tuple<int, char, char, double, double, float> as an example, for each type, we can first calculate its corresponding indices in the tuple, which is 5 for float and 3, 4 for double, then extract elements base on the indices and construct a tuple with same types, finally use tuple_cat to concatenate them together
#include <array>
#include <tuple>
#include <utility>
template<typename T, class Tuple>
constexpr auto
extract_tuple_of(const Tuple& t) {
constexpr auto N = std::tuple_size_v<Tuple>;
constexpr auto indices = []<std::size_t... Is>
(std::index_sequence<Is...>) {
std::array<bool, N> find{
std::is_same_v<std::tuple_element_t<Is, Tuple>, T>...
};
std::array<std::size_t, find.size()> indices{};
std::size_t size{};
for (std::size_t i = 0, j = 0; j < find.size(); j++) {
size += find[j];
if (find[j])
indices[i++] = j;
}
return std::pair{indices, size};
}(std::make_index_sequence<N>{});
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return std::tuple(std::get<indices.first[Is]>(t)...);
}(std::make_index_sequence<indices.second>{});
};
template<typename... Ts_out, class Tuple>
constexpr auto
extract_from_tuple(const Tuple& t) {
return std::tuple_cat(extract_tuple_of<Ts_out>(t)...);
}
Demo
constexpr auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
constexpr auto extract1 = extract_from_tuple<float, double>(tuple);
constexpr auto extract2 = extract_from_tuple<int>(tuple);
constexpr auto extract3 = extract_from_tuple<long>(tuple);
static_assert(extract1 == std::tuple<float, double, double>{4.5f, 1.2, 2.3});
static_assert(extract2 == std::tuple<int, int, int>{1, 2, 3});
static_assert(extract3 == std::tuple<>{});
Yet another solution (the shortest?), which, after inspection, is basically Barry's but without the "mp_contains":
template<typename ... Ts>
constexpr auto extract_from_tuple(auto tuple)
{
auto get_element = [](auto el) {
if constexpr ((std::is_same_v<decltype(el), Ts> || ...)) {
return std::make_tuple(std::move(el));
}
else {
return std::make_tuple();
}
};
return std::apply([&](auto ... args){
return std::tuple_cat(get_element(std::move(args)) ...);}, std::move(tuple));
}
Here is the positive result -- note however that the ordering comes from the tuple and not the template argument list:
int main()
{
static_assert( extract_from_tuple <float, double>(std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f)) == std::tuple<double, double, float>{1.2, 2.3, 4.5f});
}

accessing std::tuple element by constexpr in type

I would like to access to a tuple element at compile time by a value constexpr in the type
#include <iostream>
#include <tuple>
#include <utility>
struct A {
static constexpr int id = 1;
void work() {
std::cout << "A" << std::endl;
}
};
struct B {
static constexpr int id = 2;
void work() {
std::cout << "B" << std::endl;
}
};
int main() {
A a;
B b;
std::tuple<A,B> t = std::make_tuple(a,b);
static constexpr int search_id = 2;
auto& item = std::get< ? ( T::id == search_id ) ? >(t);
item.work();
return 0;
}
I guess using std::apply and test would be a runtime search...
I'm using c++20
Instead of std::get a single element, you can use std::apply to iterate over the elements of the tuple and perform operations based on the element type
A a;
B b;
auto t = std::make_tuple(a, b);
static constexpr int search_id = 2;
std::apply([](auto&... items) {
([]<class T>(T& item) {
if constexpr (T::id == search_id)
item.work();
}(items), ...);
}, t);
Demo
If you really want to get a single tuple element with a specific id value, you can still use std::apply to expand the id of all elements and find the offset of the value equal to search_id as the template parameter of std::get
auto& item = std::apply([&t]<class... Args>(const Args&... items) -> auto& {
constexpr auto id = [] {
std::array ids{Args::id...};
return ids.end() - std::ranges::find(ids, search_id);
}();
return std::get<id>(t);
}, t);
item.work();
You can create constrexpr function to get index:
template <typename... Ts>
constexpr std::size_t get_index(int id)
{
constexpr int ids[] = {Ts::id...};
const auto it = std::find(std::begin(ids), std::end(ids), id);
// Handle absent id.
if (it == std::end(ids)) {
throw std::runtime("Invalid id");
}
// You can also possibly handle duplicate ids.
return std::distance(std::begin(ids), it);
}
template <int id, typename... Ts>
constexpr auto& get_item(std::tuple<Ts...>& t)
{
return std::get<get_index<Ts...>(id)>(t);
}
template <int id, typename... Ts>
constexpr const auto& get_item(const std::tuple<Ts...>& t)
{
return std::get<get_index<Ts...>(id)>(t);
}
and then
auto& item = get_item<search_id>(t);
This is a prime candidate for std::disjunction, which can be used to perform a compile-time linear search; you just need a helper type to act as the predicate:
namespace detail {
template<typename T, auto Id, auto I, typename U = std::tuple_element_t<I, T>>
struct get_by_id_pred : std::bool_constant<std::remove_cvref_t<U>::id == Id> {
static constexpr auto index = I;
};
}
template<int Id>
constexpr auto&& get_by_id(auto&& t) noexcept {
using tuple_t = std::remove_cvref_t<decltype(t)>;
return [&]<auto ...Is>(std::index_sequence<Is...>) -> auto&& {
using res = std::disjunction<detail::get_by_id_pred<tuple_t, Id, Is>...>;
static_assert(res::value, "id not found");
return std::get<res::index>(decltype(t)(t));
}(std::make_index_sequence<std::tuple_size_v<tuple_t>>{});
}
...
auto& item = get_by_id<search_id>(t);
Online Demo

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>();
}

Generate Arbitrarily Nested Vectors in C++

I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(1);
std::vector<std::vector<long double>> test_vector;
test_vector.push_back(vector1);
The more complex usage of the n_dim_vector_generator function:
auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);
In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
std::vector<std::vector<long double>> vector2;
vector2.push_back(vector1);
vector2.push_back(vector1);
vector2.push_back(vector1);
std::vector<std::vector<std::vector<long double>>> vector3;
vector3.push_back(vector2);
vector3.push_back(vector2);
vector3.push_back(vector2);
//...Totally repeat 15 times in order to create test_vector2
std::vector<...std::vector<long double>> test_vector2;
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
The detail to implement n_dim_vector_generator function is as follows.
#include <iostream>
#include <vector>
template <typename T, std::size_t N>
struct n_dim_vector_type;
template <typename T>
struct n_dim_vector_type<T, 0> {
using type = T;
};
template <typename T, std::size_t N>
struct n_dim_vector_type {
using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};
template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
if (N == 0)
{
return std::move(input_data);
}
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.
template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
return std::move(input_data);
}
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?
The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3
To achieve your specific mapping of:
auto test_vector = n_dim_vector_generator<2, long double>(2, 3)
to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:
std::vector<std::vector<T>>(COUNT, std::vector<T>(...))
Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...
template <size_t N, typename T>
struct n_dim_vector_generator {
using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
type operator()(T value, size_t size) {
return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
}
};
template <typename T>
struct n_dim_vector_generator<0, T> {
using type = T;
type operator()(T value, size_t size) {
return value;
}
};
usage:
auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);
Demo: https://godbolt.org/z/eiDAUG
For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:
std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
If you wanted to reduce the boilerplate to declare one, you can use a struct for that:
template <typename T, size_t... N>
struct multi_array;
template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};
template <typename T, size_t NLast>
struct multi_array<T, NLast> {
using type = std::array<T, NLast>;
};
template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;
Then to use:
multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:
template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
constexpr bool empty() const { return (**this).empty(); }
constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};
(let the buyer beware if you use those methods with nullptr)
Then you can still brace-initialize a new expression and use it like a container:
dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
Demo: https://godbolt.org/z/lUwVE_

Replace N-th element of a std::tuple

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