user1899020
user1899020

Reputation: 13575

Get index of a tuple element's type?

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.

Upvotes: 20

Views: 16161

Answers (9)

Moreno Trlin
Moreno Trlin

Reputation: 41

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

Upvotes: 2

ABu
ABu

Reputation: 12259

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

Upvotes: 2

carmius
carmius

Reputation: 151

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

Upvotes: 1

El Mismo Sol
El Mismo Sol

Reputation: 928

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.

Upvotes: 0

Qiang
Qiang

Reputation: 537

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 */

Upvotes: 1

OznOg
OznOg

Reputation: 4722

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

Upvotes: 1

Jarod42
Jarod42

Reputation: 217145

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

Upvotes: 1

pierre
pierre

Reputation: 51

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

Upvotes: 5

Casey
Casey

Reputation: 42554

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

Upvotes: 46

Related Questions