解决std::get constexpr限制:实现tuple向量的动态元素访问
Hey there! Let's break down your problem and work through solutions step by step.
The core issue here is that std::get<N> requires N to be a compile-time constant, but your tidx is a runtime variable. To get around this, we need to generate compile-time branches for every possible index in your tuple, then dispatch to the correct one at runtime.
Here are two practical approaches depending on your needs:
Approach 1: Return a Variant (For Type-Safe Value Retrieval)
If you need to actually return the value (not just perform an action on it), wrap the return type in std::variant to handle heterogeneous types cleanly. Here's how to modify your VTS struct:
#include <vector> #include <tuple> #include <variant> #include <stdexcept> #include <utility> template<typename...T> struct VTS : private std::vector<std::tuple<T...>> { using VT = std::vector<std::tuple<T...>>; using TUP = std::tuple<T...>; using ValueVariant = std::variant<T...>; // Helper to map runtime index to compile-time tuple access template<size_t... Is> ValueVariant getvalue_impl(size_t vidx, size_t tidx, std::index_sequence<Is...>) { using FuncPtr = ValueVariant(VTS::*)(size_t); FuncPtr funcs[] = { [this](size_t idx) -> ValueVariant { return std::get<Is>(VT::at(idx)); }... }; return (this->*funcs[tidx])(vidx); } ValueVariant getvalue(size_t vidx, size_t tidx) { constexpr size_t tuple_size = std::tuple_size_v<TUP>; if (tidx >= tuple_size) { throw std::out_of_range("tidx exceeds tuple size"); } if (vidx >= VT::size()) { throw std::out_of_range("vidx exceeds vector size"); } return getvalue_impl(vidx, tidx, std::make_index_sequence<tuple_size>{}); } size_t tupsize() const { return std::tuple_size_v<TUP>; } };
Use it with std::visit to handle each type:
// Example usage VTS<int, std::string, double> myvts; myvts.emplace_back(42, "hello", 3.14); myvts.emplace_back(100, "world", 2.71); for (size_t i=0; i<myvts.size(); i++) { for (size_t j=0; j<myvts.tupsize(); j++) { auto var = myvts.getvalue(i,j); std::visit([](auto&& val) { std::cout << val << "\n"; }, var); } }
Approach 2: Use a Visitor Callback (For Direct Actions)
If your main goal is to perform an action (like printing) rather than returning the value, this approach avoids variants entirely and is more efficient:
#include <vector> #include <tuple> #include <stdexcept> #include <utility> template<typename...T> struct VTS : private std::vector<std::tuple<T...>> { using VT = std::vector<std::tuple<T...>>; using TUP = std::tuple<T...>; template<typename Func> void apply(size_t vidx, size_t tidx, Func&& func) { constexpr size_t tuple_size = std::tuple_size_v<TUP>; if (tidx >= tuple_size) { throw std::out_of_range("tidx exceeds tuple size"); } if (vidx >= VT::size()) { throw std::out_of_range("vidx exceeds vector size"); } // Dispatch to the correct compile-time index [&]<size_t... Is>(std::index_sequence<Is...>) { ((tidx == Is ? (func(std::get<Is>(VT::at(vidx))), true) : false) || ...); }(std::make_index_sequence<tuple_size>{}); } size_t tupsize() const { return std::tuple_size_v<TUP>; } };
Usage is clean and direct:
VTS<int, std::string, double> myvts; myvts.emplace_back(42, "hello", 3.14); myvts.emplace_back(100, "world", 2.71); for (size_t i=0; i<myvts.size(); i++) { for (size_t j=0; j<myvts.tupsize(); j++) { myvts.apply(i, j, [](auto&& val) { std::cout << val << "\n"; }); } }
You mentioned needing to store groups of different types—let's go over viable alternatives:
Custom Structs: If the set of types is fixed (not template-driven), a named struct is more readable than a tuple. For example:
struct MyData { int id; std::string name; double value; }; std::vector<MyData> my_collection;This gives you explicit member names instead of index-based access, making code easier to maintain.
std::any (Type Erasure): You could use
std::vector<std::vector<std::any>>, but this loses compile-time type safety. You'd have to manually cast values back to their original types, which is error-prone and less efficient.Boost.Fusion Containers: Libraries like
boost::fusion::vectoroffer more runtime reflection capabilities than standard tuples, but they require linking against Boost. This is a good option if you need advanced heterogeneous container features.Variant of Structs: If your rows might be different "types" of groups (not just fixed heterogeneous elements), you could use
std::vector<std::variant<StructA, StructB>>, but this doesn't match your original requirement of fixed groups of different types per row.
In most cases, std::tuple is still the best standard-library choice for fixed heterogeneous element groups, especially when working with template code. The runtime access workaround we covered earlier is a standard way to bridge the compile-time nature of tuples with runtime index needs.
内容的提问来源于stack exchange,提问作者anon

