Get index of a tuple element's type?
Asked Answered
R

9

19

If I have a tuple with different element types like

std::tuple<T0, T1, T2, ...>

And how to get the index of a element type?

template<class T, class Tuple>
struct Index
{
    enum {value = ?;}
};

Thanks.

Romany answered 5/8, 2013 at 16:49 Comment(4)
What happens if you have std::tuple<int, float, int, std::string> and you ask for int? Also why do you need this?Deccan
I assume the tuple has different element types. If no such assumption, get any one match is fine. I use it to implement an efficient abstract factory by a given a base class's derived classes tuple.Romany
Are you looking for it for a specific environment, or just general C++?Forepart
No specific environment.Romany
P
45
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;
};

See it live at Coliru.

This implementation returns the index of the first occurrence of a given type. Asking for the index of a type that is not in the tuple results in a compile error (and a fairly ugly one at that).

Petes answered 5/8, 2013 at 16:59 Comment(8)
If a type is not among the element types, what is the behavior?Romany
@Romany Compile-time error.Petes
I test `std::cout << "Index<float, foo_t> = " << Index<char*, foo_t>::value << std::endl;' in Coliru but gives 1.Romany
@Romany Try changing the type in the actual expression, instead of the output string.Petes
@Petes A static_assert can help with the cryptic error message.Engdahl
Defining the body of default Index template with static_assert(!std::is_same_v<Tuple, std::tuple<>>, "Could not find `T` in given `Tuple`"); would yield meaning full error message when type is not found.Economically
@Petes link to coliru seems to be broken, can you supply the code to Try It Online!? Their sharelinks won't break :)Disrepute
You might even replace const with constexpr.Firer
S
5
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>();
};

The example above avoids generating tons of sub tuples, which makes compilation fail (out of memory) when you call index_in_tuple for large tuples

Santanasantayana answered 26/3, 2020 at 13:30 Comment(0)
N
1

Yet another one using fold expression. It also sets the value to -1 when not found.

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

live: https://onlinegdb.com/SJE8kOYdv

EDIT:

As suggested by @Jarod42, one may use 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;

In case of duplicate type, this version returns the index of the last one.

live: https://onlinegdb.com/WenEBQs0L

Nanji answered 30/10, 2020 at 10:48 Comment(2)
I'm not sure I understand your proposal, the std::min(...) will always return 0 in this case, isn't it? Using max would work but give the index of the last one in case of duplicate (which may be good enough)Nanji
Oups, indeed, comment was mostly to highlight issue with duplicates; max for the last one might be ok.Erinaceous
E
1

With constexpr "function" (or lambda), you might do

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

Actually requires C++20 as missing constexpr for std functions, but can easily be rewritten for previous version. (C++11 would be trickier with the strong restriction for constexpr).

Erinaceous answered 13/10, 2021 at 15:38 Comment(0)
B
1
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...>();
}

usage

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 */
Boyce answered 15/3, 2022 at 6:12 Comment(0)
N
1

This does what Qiang does, but it doesn't have that strange looking empty else branch.

It also makes sure that a tuple with unique types gets passed to it for good measure.

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...>();
}
Nakasuji answered 1/12, 2022 at 13:27 Comment(0)
G
0

Try this one, which reports error if the tuple is empty, T doesn't exist or not unique in the tuple:

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;

Example:

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.
Gesture answered 1/3, 2022 at 3:43 Comment(0)
D
0

I don't think you can do it shorter than this, and works in C++17 (inspired by @Jaron42 answer):

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

Test case:

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

If the tuple types are copyable and default constructibles, and the queried type is guaranteed to exists in the tuple, then this also works:

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();
}
Dys answered 21/1 at 0:27 Comment(0)
S
0

Another short version:

template<class Attribute, class... Attributes>
constexpr std::size_t index_of(const std::tuple<Attributes...>&)
{
     std::size_t i = 0;
     bool found = ((++i && std::is_same_v < Attribute, Attributes>()) || ...);

     return i - found;
}
Shari answered 11/6 at 13:56 Comment(2)
@Андрей Куликов please stop to edit this answer. The parentheses are correct and your attempt to remove them breaks this answer. You need a conversion to bool either through value or operator(). Please do not propose the same change over and over again.Mainmast
Agree with Friedrich, stop editing also my other posts with your minor changes, it helps nobody and it is not useful.Shari

© 2022 - 2024 — McMap. All rights reserved.