如何获取元组元素的位置

3
例如,我有一个元组。
std::tuple<int, int, int, int> a(2, 3, 1, 4);

我希望通过以下函数获取其元素的位置。

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

这里2的位置是0,3的位置是1,1的位置是3,4的位置是3。如何实现该功能?一个愚蠢的方法是

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
}

有更好的方法吗?谢谢。

5
为什么你不使用 std::array<int, 4> 呢?因为 std::tuple 中的元素可以是不同的类型,所以通常不会对其进行迭代,这并不太合理。 - Joseph Mansfield
我刚刚通过使用int(s)简化了我的问题。实际上它们是不同的类型,但都继承自一个基类。 - user1899020
1
那为什么不用 std::array<Base*, 4> 呢? :P - Joseph Mansfield
这可能是一个设计缺陷。std::tuple 的设计是用于位置已知但值未知的场景。 - Drew Dormann
1
@user1899020:我编辑了我的答案,提供了一种替代、更简单(且更高效)的解决方案。也许你可以检查一下它是否符合你的需求(或者原始版本是否符合)。你最初接受的答案已被作者删除,因为它存在一个难以修复的非终止缺陷。 - Andy Prowl
显示剩余4条评论
3个回答

6

更新:

最终我找到了一种更简单的方法来实现这个目标,它还使用了短路技术(因此比较次数更少)。

假设有一些机制:

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

客户端最终要调用的函数归结为这个简单的跳板:
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));
}

最后,这是您在程序中使用它的方式:

#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)
}

这里有一个实时示例


编辑:

如果您想进行反向搜索,您可以用以下版本替换上述机制和蹦床函数:

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

这里有一个实时样例


原始答案:

这不像是使用元组的典型方式,但如果您真的想这样做,那么以下是一种方法(适用于任何大小的元组)。

首先,一些机制(众所周知的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;

接下来是一对重载函数模板:

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

这是如何使用它的方法:

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


不错的例子,但遗憾的是没有短路评估 :) - Xeo
非常好,但据我所知,只有当T和所有的Args...是可比较的时候才能工作 - 这对于一堆整数来说是成立的,但对于OP的情况可能不成立。 - Arne Mertz
@ArneMertz:可以轻松修改以接受额外的“predicate”参数。 - Xeo
@ArneMertz:嗯,是的,确实如此。我假设OP打算提供Ts,这些类型可与Args...中的所有类型进行比较。 - Andy Prowl
1
@AndyProwl 根据我看来,这会给出 val 的 最后 出现的索引,因为它是向后迭代的,对吧? - Arne Mertz
显示剩余7条评论

2

这个答案比普遍接受的答案稍微短一些,它向前搜索而不是向后搜索(因此它找到的是第一个匹配项,而不是最后一个匹配项),并且使用了 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>());
}

很好的发现,将in_range谓词作为函数参数提供! - Arne Mertz
@JonathanWakely:我编辑了我的答案并包括了向前和向后搜索的替代方案 :) - Andy Prowl

0

根据评论进行修改。通过修改取消的答案来实现简单的方法。

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

把它用作

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

这看起来可行。


这仍会导致无限递归实例化。运行时条件与编译时递归不兼容。 - Xeo
@Xeo:它不会导致无限递归,因为它不是递归的——它甚至没有调用自身。所以如果它能工作,那只是偶然。 - Andy Prowl
@Andy:啊,我看到用户可能想要写什么了。 :P - Xeo
你在那个修改中忘记了递归 ;) - Arne Mertz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接