skip to Main Content

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


  1. Chosen as BEST ANSWER

    The correct code is, thanks to @Eljay:

    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)) {
          if constexpr ((t_idx + 1) < std::tuple_size_v<t_tuple>) {
            traverse_tuple_values<t_tuple, t_function, t_idx + 1>(p_function,
                                                                  p_tuple);
          }
        }
      }
    }
    

  2. You check if constexpr (t_idx < std::tuple_size_v<t_tuple>). But when index 2 is reached, the compiler tries to instantiate traverse_tuple_values<..., 3> because t_idx + 1 == 2. Then the compiler checks the constraints for the function but visit_tuple_element_value with t_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:

    template<typename Func, typename... Args>
    void traverse_tuple_values_with_index(Func function, const std::tuple<Args...>& tup) {
        [&]<std::size_t ... Is>(std::index_sequence<Is...>) {
            (function.template operator()<Is>(tup), ...);
        }(std::make_index_sequence<sizeof...(Args)>());
    }
    

    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;

    template<typename Func, typename... Args>
    void traverse_tuple_values(Func function, const std::tuple<Args...>& tup) {
        [&]<std::size_t ... Is>(std::index_sequence<Is...>) {
            (function(std::get<Is>(tup)), ...);
        }(std::make_index_sequence<sizeof...(Args)>());
    }
    

    See demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search