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.
CodePudding user response:
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
CodePudding user response:
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]);
}
}
CodePudding user response:
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:
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>();
}
