通过类型中的 constexpr 访问 std::tuple 元素

accessing std::tuple element by constexpr in type

我想在编译时通过类型中的值 constexpr 访问元组元素

#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;
}

我想使用 std::apply 和测试将是一个运行时搜索... 我正在使用 c++20

代替std::get单个元素,您可以使用std::apply遍历元组的元素并根据元素类型执行操作

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

如果你真的想得到一个具有特定id值的单个元组元素,你仍然可以使用std::apply扩展所有元素的id并找到偏移量等于 search_id 的值作为 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();

您可以创建 constrexpr 函数来获取索引:

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

然后

auto& item = get_item<search_id>(t);

这是 std::disjunction 的主要候选者,可用于执行 compile-time 线性搜索;你只需要一个辅助类型来充当谓词:

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