3

For example, I have a tuple

std::tuple<int, int, int, int> a(2, 3, 1, 4);

and I want to get the position of its elements using such as the the following function.

int GetPosition(const std::tuple<int, int, int, int>& tp, int element);

Here 2's position is 0, 3's position is 1, 1's position is 3 and 4'position is 3. How to implement the function? A silly way is to

int GetPosition(const std::tuple<int, int, int, int>& tp, int element)
{
    if (std::get<0>(tp) == element) return 0;
    if (std::get<1>(tp) == element) return 1;
    if (std::get<2>(tp) == element) return 2;
    ... // Write as more as an allowed max number of elements
}

Any better ways? Thanks.

user1899020
  • 13,167
  • 21
  • 79
  • 154
  • 5
    Any reason why you're not using a `std::array`? It doesn't usually make much sense to iterate over elements in a `std::tuple`, since they can be of different types. – Joseph Mansfield Apr 08 '13 at 13:46
  • I just simplified my question by using int(s). They are in fact different types but all inherit from a base class. – user1899020 Apr 08 '13 at 13:48
  • 1
    So why not `std::array`? :P – Joseph Mansfield Apr 08 '13 at 13:51
  • I want to know the concrete types in some other methods. – user1899020 Apr 08 '13 at 13:54
  • This is probably a design flaw. A `std::tuple` is designed for scenarios where the position is known and the value isn't. – Drew Dormann Apr 08 '13 at 13:54
  • @DrewDormann I would go further and say that the type at each position should be known too. – Nicu Stiurca Apr 08 '13 at 13:55
  • And what would you do with the position? You cannot use it in a meaningful way, since to access a tuple element, you'd need a compiletime constant i. You normally don't need to know the concrete types in other methods - use virtual functions, visitor pattern, whatever. – Arne Mertz Apr 08 '13 at 14:00
  • 1
    @user1899020: I edited my answer to provide an alternative, simpler solution (and more efficient). Maybe you could check if it fits your needs (or if the original version does). The answer you originally accepted has been deleted by the author because it had a flaw with non-termination that was no trivial to fix. – Andy Prowl Apr 08 '13 at 14:38
  • @AndyProwl I give a simple answer based on the canceled answer. But I cannot see any flaw for this answer. – user1899020 Apr 08 '13 at 15:03

3 Answers3

6

UPDATE:

I eventually figured out a way to achieve this in a simpler way that also uses short-circuiting (and therefore performs less comparisons).

Given some machinery:

namespace detail
{
    template<int I, int N, typename T, typename... Args>
    struct find_index
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<I>(t) == val) ? I :
                find_index<I + 1, N, T, Args...>::call(t, std::forward<T>(val));
        }
    };

    template<int N, typename T, typename... Args>
    struct find_index<N, N, T, Args...>
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<N>(t) == val) ? N : -1;
        }
    };
}

The function that clients are going to invoke eventually boils down to this simple trampoline:

template<typename T, typename... Args>
int find_index(std::tuple<Args...> const& t, T&& val)
{
    return detail::find_index<sizeof...(Args), T, Args...>::
           call(t, std::forward<T>(val));
}

Finally, this is how you would use it in your program:

#include <iostream>

int main()
{
    std::tuple<int, int, int, int> a(2, 3, 1, 4);
    std::cout << find_index(a, 1) << std::endl; // Prints 2
    std::cout << find_index(a, 2) << std::endl; // Prints 0
    std::cout << find_index(a, 5) << std::endl; // Prints -1 (not found)
}

And here is a live example.


EDIT:

If you want to perform the search backwards, you can replace the above machinery and the trampoline function with the following versions:

#include <tuple>
#include <algorithm>

namespace detail
{
    template<int I, typename T, typename... Args>
    struct find_index
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<I - 1>(t) == val) ? I - 1 :
                find_index<I - 1, T, Args...>::call(t, std::forward<T>(val));
        }
    };

    template<typename T, typename... Args>
    struct find_index<0, T, Args...>
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<0>(t) == val) ? 0 : -1;
        }
    };
}

template<typename T, typename... Args>
int find_index(std::tuple<Args...> const& t, T&& val)
{
    return detail::find_index<0, sizeof...(Args) - 1, T, Args...>::
           call(t, std::forward<T>(val));
}

Here is a live example.


ORIGINAL ANSWER:

This does not really sound like a typical way one would use tuples, but if you really want to do this, then here is a way (works with tuples of any size).

First, some machinery (the well-known indices trick):

template <int... Is>
struct index_list { };

namespace detail
{
    template <int MIN, int N, int... Is>
    struct range_builder;

    template <int MIN, int... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    template <int MIN, int N, int... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    { };
}

template<int MIN, int MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

Then, a couple of overloaded function templates:

#include <tuple>
#include <algorithm>

template<typename T, typename... Args, int... Is>
int find_index(std::tuple<Args...> const& t, T&& val, index_list<Is...>)
{
    auto l = {(std::get<Is>(t) == val)...};
    auto i = std::find(begin(l), end(l), true);
    if (i == end(l)) { return -1; }
    else { return i - begin(l); }
}

template<typename T, typename... Args>
int find_index(std::tuple<Args...> const& t, T&& val)
{
    return find_index(t, std::forward<T>(val), 
                      index_range<0, sizeof...(Args)>());
}

And here is how you would use it:

#include <iostream>

int main()
{
    std::tuple<int, int, int, int> a(2, 3, 1, 4);
    std::cout << find_index(a, 1) << std::endl; // Prints 2
    std::cout << find_index(a, 2) << std::endl; // Prints 0
    std::cout << find_index(a, 5) << std::endl; // Prints -1 (not found)
}

And here is a live example.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
2

Slightly shorter than the accepted answer, and searching forwards not backwards (so it finds the first match, not the last match), and using constexpr:

#include <tuple>

template<std::size_t I, typename Tu>
    using in_range = std::integral_constant<bool, (I < std::tuple_size<Tu>::value)>;

template<std::size_t I1, typename Tu, typename Tv>
constexpr int chk_index(const Tu& t, Tv v, std::false_type)
{
    return -1;
}

template<std::size_t I1, typename Tu, typename Tv>
constexpr int chk_index(const Tu& t, Tv v, std::true_type)
{
    return std::get<I1>(t) == v ? I1 : chk_index<I1+1>(t, v, in_range<I1+1, Tu>());
}

template<typename Tu, typename Tv>
constexpr int GetPosition(const Tu& t, Tv v)
{
    return chk_index<0>(t, v, in_range<0, Tu>());
}
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
0

Modified based on comments. A simple way by modifying the canceled answer

template<class Tuple>
struct TupleHelper
{
    TupleHelper(Tuple& _tp) : tp(_tp) {}

    Tuple& tp;

    template<int N>
    int GetPosition(int element)
    {
        if (std::get<N>(tp) == element) return N;
        return GetPosition<N+1>(element);
    }     

    template<>
    int GetPosition<std::tuple_size<Tuple>::value>(int element)
    {
        return -1;
    }  
};    

use it as

TupleHelper<MyTupleTy>(myTuple).GetPosition<0>(element);

This seems work.

Catskul
  • 17,916
  • 15
  • 84
  • 113
user1899020
  • 13,167
  • 21
  • 79
  • 154