1

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

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
Deub
  • 23
  • 4
  • 1
    Are the types unique? Can you share your `std::apply`-based solution? – HolyBlackCat Mar 08 '22 at 10:48
  • type are different, sorry I can not figure out how to format comment, but apply would be like the solution proposed below...I just can not understand how iterating trought values will allow to access directly to the right element at compile time – Deub Mar 08 '22 at 12:04

3 Answers3

3

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();
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • thanks, but I guess in this case we are accessing each element of the tuple , so it s not a compile time access, isn't it? – Deub Mar 08 '22 at 10:58
  • 1
    All element access is done at compile-time since the index must be a compile-time constant. – 康桓瑋 Mar 08 '22 at 11:03
2

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);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • thank you very much, but as far as I understand std::find won't be compute at compile-time, so the position of element will be calculated at runtime, isn't it? – Deub Mar 08 '22 at 11:59
  • [`std::find`](https://en.cppreference.com/w/cpp/algorithm/find) is `constexpr` since C++20 (for previous standard, you have to write your own version :( ). The index selection is actually done at compile time, as expected. – Jarod42 Mar 08 '22 at 12:32
2

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

ildjarn
  • 62,044
  • 9
  • 127
  • 211