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.

user1899020
  • 13,167
  • 21
  • 79
  • 154

7 Answers7

44
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).

Casey
  • 41,449
  • 7
  • 95
  • 125
  • If a type is not among the element types, what is the behavior? – user1899020 Aug 05 '13 at 17:19
  • @user1899020 [Compile-time error.](http://coliru.stacked-crooked.com/view?id=00d44c11b2bfb4ce09cd43c8bfe57739-c944ac6b0eed3bfc48b1f78a894f6965) – Casey Aug 05 '13 at 17:20
  • I test `std::cout << "Index = " << Index::value << std::endl;' in Coliru but gives 1. – user1899020 Aug 05 '13 at 17:28
  • 4
    @user1899020 Try changing the type in the actual expression, instead of the output string. – Casey Aug 05 '13 at 18:20
  • 1
    @Casey A `static_assert` can help with the cryptic error message. – OutOfBound Jul 28 '17 at 17:52
  • Defining the body of default `Index` template with ```static_assert(!std::is_same_v>, "Could not find `T` in given `Tuple`");``` would yield meaning full error message when type is not found. – xiay Apr 27 '19 at 00:21
  • 1
    @Casey link to coliru seems to be broken, can you supply the code to [Try It Online!](http://tio.run)? Their sharelinks won't break :) – jaaq Sep 24 '20 at 12:26
  • You might even replace `const` with `constexpr`. – eyelash Mar 02 '23 at 08:45
4
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

Marc Dirven
  • 309
  • 2
  • 18
pierre
  • 41
  • 1
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

OznOg
  • 4,440
  • 2
  • 26
  • 35
  • 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) – OznOg Oct 13 '21 at 14:45
  • Oups, indeed, comment was mostly to highlight issue with duplicates; max for the last one might be ok. – Jarod42 Oct 13 '21 at 15:22
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).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
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 */
Qiang
  • 455
  • 5
  • 15
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.
El Mismo Sol
  • 869
  • 1
  • 6
  • 14
0

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...>();
}
Simon Farre
  • 71
  • 1
  • 6