如果我有一个具有不同元素类型的元组,例如
std::tuple<T0, T1, T2, ...>
如何获取元素类型的索引?
template<class T, class Tuple>
struct Index
{
enum {value = ?;}
};
谢谢。
template <class T, class Tuple>
struct Index;
template <class T, class... Types>
struct Index<T, std::tuple<T, Types...>> {
static const std::size_t value = 0;
};
template <class T, class U, class... Types>
struct Index<T, std::tuple<U, Types...>> {
static const std::size_t value = 1 + Index<T, std::tuple<Types...>>::value;
};
此实现返回给定类型第一次出现的索引。请求不在元组中的类型的索引会导致编译错误(而且是一个相当难看的错误)。
template< size_t I, typename T, typename Tuple_t>
constexpr size_t index_in_tuple_fn(){
static_assert(I < std::tuple_size<Tuple_t>::value,"The element is not in the tuple");
typedef typename std::tuple_element<I,Tuple_t>::type el;
if constexpr(std::is_same<T,el>::value ){
return I;
}else{
return index_in_tuple_fn<I+1,T,Tuple_t>();
}
}
template<typename T, typename Tuple_t>
struct index_in_tuple{
static constexpr size_t value = index_in_tuple_fn<0,T,Tuple_t>();
};
上面的例子避免了生成大量的子元组,当你为大元组调用
index_in_tuple
时,这会导致编译失败(内存不足)。
constexpr
“函数”(或 lambda),你可能会这样做
template <class T, class Tuple>
struct Index;
template <class T, typename... Ts>
struct Index<T, std::tuple<Ts...>>
{
static constexpr std::size_t index = [](){
constexpr std::array<bool, sizeof...(Ts)> a{{ std::is_same<T, Ts>::value... }};
// You might easily handle duplicate index too (take the last, throw, ...)
// Here, we select the first one.
const auto it = std::find(a.begin(), a.end(), true);
// You might choose other options for not present.
// As we are in constant expression, we will have compilation error.
// and not a runtime expection :-)
if (it == a.end()) throw std::runtime_error("Not present");
return std::distance(a.begin(), it);
}();
};
实际上需要 C++20 作为 std 函数缺少 constexpr
, 但可以很容易地重写以前的版本。 (对于
constexpr
的严格限制,C++11 会更加棘手)。
template <class X, class Tuple>
class Idx;
template <class X, class... T>
class Idx<X, std::tuple<T...>> {
template <std::size_t... idx>
static constexpr ssize_t find_idx(std::index_sequence<idx...>) {
return -1 + ((std::is_same<X, T>::value ? idx + 1 : 0) + ...);
}
public:
static constexpr ssize_t value = find_idx(std::index_sequence_for<T...>{});
};
直播:https://onlinegdb.com/SJE8kOYdv
编辑:根据@Jarod42的建议,可以使用 std::max:
template <class X, class Tuple>
class Idx;
template <class X, class... T>
class Idx<X, std::tuple<T...>> {
template <std::size_t... idx>
static constexpr ssize_t find_idx(std::index_sequence<idx...>) {
return std::max({static_cast<ssize_t>(std::is_same_v<X, T> ? idx : -1)...});
}
public:
static constexpr ssize_t value = find_idx(std::index_sequence_for<T...>{});
};
template<typename X, class Tuple>
inline constexpr ssize_t Idx_v = Idx<X, Tuple>::value;
如果是重复类型,此版本返回最后一个的索引。直播:
template <typename T, typename U, typename... Us>
constexpr auto getIndex() {
if constexpr (is_same_v<T, U>) {
return 0;
} else {
if constexpr (sizeof...(Us)) {
return 1 + getIndex<T, Us...>();
} else {}
}
}
template <typename T, typename U, typename... Us>
constexpr auto getIndex(const tuple<U, Us...> &) {
return getIndex<T, U, Us...>();
}
用法
tuple the_tuple{'\0', 1, 2L, 3.0, "4", string{"5"}};
cout << getIndex<char>(the_tuple) << endl; // 0
cout << getIndex<double>(the_tuple) << endl; // 3
cout << getIndex<const char *>(the_tuple) << endl; // 4
cout << getIndex<string>(the_tuple) << endl; // 5
/* cout << getIndex<short>(the_tuple) << endl; // compile error */
它还确保将具有唯一类型的元组传递给它以进行良好的测量。
template <typename...>
inline constexpr auto is_unique = std::true_type{};
template <typename T, typename... Rest>
inline constexpr auto is_unique<T, Rest...> = std::bool_constant<(!std::is_same_v<T, Rest> && ...) && is_unique<Rest...>>{};
template <typename T, typename U, typename... Us>
constexpr auto getIndexImpl() {
if constexpr (std::is_same<T, U>::value) {
return 0;
} else {
static_assert(sizeof...(Us) > 0, "This tuple does not have that type");
return 1 + getIndexImpl<T, Us...>();
}
}
template <typename T, typename U, typename... Us>
constexpr auto getIndex(const std::tuple<U, Us...> &) {
static_assert(is_unique<U, Us...>, "getIndex should only be called on tuples with unique types.");
return getIndexImpl<T, U, Us...>();
}
template <template <typename ...> class TT, std::size_t I, typename ...Ts>
struct defer
{
using type = TT<I, Ts...>;
};
template <std::size_t, typename, typename>
struct tuple_index_helper;
template <std::size_t I, typename T, typename U, typename ...Vs>
struct tuple_index_helper<I, T, std::tuple<U, Vs...>>
{
static_assert(!std::is_same_v<T, U>, "Type not unique.");
static constexpr std::size_t index = tuple_index_helper<I, T, std::tuple<Vs...>>::index;
};
template <std::size_t I, typename T>
struct tuple_index_helper<I, T, std::tuple<>>
{
static constexpr std::size_t index = I;
};
template <std::size_t, typename, typename>
struct tuple_index;
template <std::size_t I, typename T, typename U, typename ...Vs>
struct tuple_index<I, T, std::tuple<U, Vs...>>
{
static constexpr std::size_t index = std::conditional_t<std::is_same_v<T, U>, defer<tuple_index_helper, I, T, std::tuple<Vs...>>, defer<tuple_index, I + 1, T, std::tuple<Vs...>>>::type::index;
};
template <std::size_t I, typename T>
struct tuple_index<I, T, std::tuple<>>
{
static_assert(!(I == 0), "Empty tuple.");
static_assert(!(I != 0), "Type not exist.");
};
template <typename T, typename U>
inline constexpr std::size_t tuple_index_v = tuple_index<0, T, U>::index;
示例:
std::tuple<int, float, const char*> t1{};
std::tuple<int, float, int> t2{};
std::tuple<> t3{};
constexpr auto idx = tuple_index_v<float, decltype(t1)>; // idx = 1
// constexpr auto idx2 = tuple_index_v<long long, decltype(t1)> // Error: Type not exist.
// constexpr auto idx3 = tuple_index_v<int, decltype(t2)> // Error: Type not unique.
// constexpr auto idx4 = tuple_index_v<int, decltype(t3)> // Error: Empty tuple.
template<class T, class... Ts>
constexpr std::size_t index_of(const std::tuple<Ts...>&)
{
int found{}, count{};
((!found ? (++count, found = std::is_same_v<T, Ts>) : 0), ...);
return found ? count - 1 : count;
}
int main(int argc, char** argv)
{
std::tuple tup{"hi", 3, 4.};
std::cout << index_of<char const*>(tup) << '\n'; // Prints 0
std::cout << index_of<double>(tup) << '\n'; // Prints 2
std::cout << index_of<std::string>(tup) << '\n'; // Prints tuple size
return 0;
}
如果元组类型是可复制且默认可构造的,并且保证查询的类型存在于元组中,那么这也适用:
template<class T, class... Ts>
constexpr std::size_t index_of_2(const std::tuple<Ts...>& tup)
{
std::variant<Ts...> var{T{}};
return var.index();
}