My goal is to have a generic way to traverse a std::tupple
, and the following code tries to show it:
#include <iostream>
#include <tuple>
using namespace std;
template <typename t, size_t t_idx, typename t_tuple>
concept visit_tuple_element_value = requires(t &&p_t, const t_tuple &p_tuple) {
{
p_t.template operator()<t_idx>(
std::declval<std::add_const_t<std::add_lvalue_reference_t<t_tuple>>>())
} -> std::same_as<bool>;
};
template <typename t_tuple, typename t_function, size_t t_idx = 0>
requires(visit_tuple_element_value<t_function, t_idx, t_tuple>)
void traverse_tuple_values(t_function p_function, const t_tuple &p_tuple) {
if constexpr (t_idx < std::tuple_size_v<t_tuple>) {
if (p_function.template operator()<t_idx>(p_tuple)) {
traverse_tuple_values<t_tuple, t_function, t_idx + 1>(p_function,
p_tuple);
}
}
}
struct a {};
struct b {};
struct c {};
using my_tuple = std::tuple<b, a, c>;
int main() {
my_tuple _tuple;
auto _visitor = [&]<size_t t_idx>(const my_tuple & /*p_tuple*/) {
if constexpr (std::is_same_v<std::tuple_element_t<t_idx, my_tuple>, a>) {
std::cout << "'a' is at index " << t_idx << std::endl;
} else {
std::cout << "'a' is NOT at index " << t_idx << std::endl;
}
return true;
};
traverse_tuple_values(_visitor, _tuple);
return 0;
}
I am getting the error:
'traverse_tuple_values<std::tuple<b, a, c>, (lambda at main.cpp:63:19), 3UL>'
I do not understand why p_function.template operator()<t_idx>(p_tuple)
, and therefore _visitor
, is being called with t_idx
equals to 3, if I have in traverse_tuple_values
the line if constexpr (t_idx < std::tuple_size_v<t_tuple>)
.
g++ --version
reports g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
2
Answers
The correct code is, thanks to @Eljay:
You check
if constexpr (t_idx < std::tuple_size_v<t_tuple>)
. But when index 2 is reached, the compiler tries to instantiatetraverse_tuple_values<..., 3>
becauset_idx + 1
== 2. Then the compiler checks the constraints for the function butvisit_tuple_element_value
witht_idx = 3
fails.To solve your problem, you could check for a termination criterion, e.g.
if constexpr (t_idx >= std::tuple_size_v<t_tuple>)
and not call itself in this case. Then you don’t need the function you want to apply to all elements to return a bool and the concept is not needed anymore either.Or you can be more explicit with fold expressions:
this code directly calls
function
once with all indices as template argument.Alternatively you can remove the need for the index and operate on the element instead on the tuple + index;
See demo