【问题标题】:find a tuple in a tuple of tuples在元组的元组中找到一个元组
【发布时间】:2020-01-09 01:59:27
【问题描述】:

我有一组类,如 A、B、C 和一个包含这些类的元组,如下所示:

struct A {
    std::string name{"a"};
};

struct B {
    std::string name{"b"};
};

struct C {
    std::string name{"c"};
};

// only first items A(), B(), C() do matter, other are arbitrary
auto t = std::make_tuple(
    std::make_tuple(A(), 1, 2, 3),
    std::make_tuple(B(), 4, 5, 6),
    std::make_tuple(C(), 7, 8)
);

我的目标逻辑是通过匹配第一个元素的类型从容器元组中选择一个元组。所以,通过上面的例子,我想得到字符串'b',当调用这样的东西时:

std::string the_b = having_first_of_type<B, decltype(t)>::get().name;

我正在尝试使用模板获得解决方案:

// a template for getting first item from N-th tuple int Tuples
template <std::size_t N, typename... Tuples>
using first_of_nth = std::tuple_element<0, std::tuple_element<N, std::tuple<Tuples...>>>;

template <std::size_t N, class T, class... Tuples>
struct having_first_of_type;

template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
                   typename std::enable_if<std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr>
{
    static auto& get(const std::tuple<Tuples...>& tuple) {
        return std::get<N>(tuple);
    }
};

template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
                   typename std::enable_if<!std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr> : having_first_of_type<N-1, T, Tuples...>;

template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<0, T, Tuples...> {}

而且我无法以正确的方式形成专业。对于第一个(std::is_same 为真)编译器说:error: expected '&gt;' for position of '= nullptr'。看起来它不接受 T* 的默认值,但我很困惑为什么..

什么是错误?或者,也许有更好的方法来获得我想要的东西?

更新 以下是 2 个可行的解决方案:来自 N. Shead 和 @n314159 - 谢谢!

我忘了提到我尝试使用 C++14 来获取它,但解决方案是针对 C++17 的。

C++17 也可以。

【问题讨论】:

  • item() 应该是内部tuple 对象,或者它的第一个元素,类型为B?你说“从容器元组中选择一个元组”,然后有 ...item().name 虽然std::tuple 没有name 成员。
  • 是的,@aschepler,你是对的,当然item() 是错的——应该有get()

标签: c++ templates stdtuple


【解决方案1】:

分配一个样式所属的 nullptr 没有意义。你应该删除它。此外,我不完全确定,出了什么问题。我们可以通过使用std::get 模板在类型而不是索引上的版本使整个事情变得更容易一些,然后我们就不必携带 N:

#include <tuple>
#include <type_traits>
#include <iostream>

template<class T, class Tup, bool negated = false>
using first_of = std::enable_if_t<negated ^ std::is_same_v<std::tuple_element_t<0, Tup>, T>>;

template<class T, class= void, class... Tups>
struct first_match_impl;

template<class T, class Tup1, class... Tups> 
struct first_match_impl<T, first_of<T, Tup1>, Tup1, Tups...> {
    using type = Tup1;

    template<class FullTup>
    static Tup1& get(FullTup& t) {
        return std::get<Tup1>(t);
    }
};

template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1, true>, Tup1, Tups...>: first_match_impl<T, void, Tups...> {};

template<class T, class... Tups>
using first_match = first_match_impl<T, void, Tups...>;

template<class T, class... Tups>
auto& get_first_of(std::tuple<Tups...> &t) {
    return first_match<T, Tups...>::get(t);
}

int main()
{
    std::tuple<std::tuple<int, float>, std::tuple<char, double>> t {{1,2.}, {'A', 4.}};
    std::cout << std::get<0>(get_first_of<char>(t)); // prints A
}

请注意,当您的元组中有两个完全相同的元组时,这将不会编译,但如果存在具有相同第一个元素的不同元组(然后它将选择其中的第一个),则会编译。

编辑: 这启发了我编写一个小型库,为元组提供类似迭代器的支持。见here

【讨论】:

  • 酷,它适用于 c++17,以为我忘了提到我使用 c++14.. 但这绝对是一个可行的解决方案
  • @atlascoder 如果将_t_v 替换为适当的定义,它可以用于C++14,如template&lt;class T, class Tup, bool negated = false&gt; using first_of = typename std::enable_if&lt;negated ^ std::is_same&lt;typename std::tuple_element&lt;0, Tup&gt;::type, T&gt;::value&gt;::type;。我的解决方案确实需要 C++17 对于if constexpr
  • 是的,我明白了。您的解决方案符合我的逻辑,也是我一直试图得到的 - 谢谢!
  • @atlascoder 查看我对答案的编辑。我写了一个小型库来概括这一点。
【解决方案2】:

您试图在编译器需要具体类型的地方提供默认值。

我假设您想要获取整个内部元组? 在这种情况下,我解决这个问题的尝试看起来像这样:

template <typename T, typename Tuple>
constexpr bool tuple_first_type_is() {
    if constexpr (std::tuple_size_v<Tuple> == 0)
        return false;
    else
        return std::is_same_v<T, std::tuple_element_t<0, Tuple>>;
}

template <typename T, std::size_t I, typename NestedTuple>
constexpr decltype(auto) having_first_of_type_impl(NestedTuple&& nested_tuple) noexcept {
    using D = std::decay_t<NestedTuple>;
    static_assert(I < std::tuple_size_v<D>, "type not found in tuple");
    using ith_tuple = std::tuple_element_t<I, D>;

    if constexpr (tuple_first_type_is<T, ith_tuple>())
        return std::get<I>(std::forward<NestedTuple>(nested_tuple));
    else
        return having_first_of_type_impl<T, I+1>(std::forward<NestedTuple>(nested_tuple));
}

template <typename T, typename NestedTuple>
constexpr decltype(auto) having_first_of_type(NestedTuple&& nested_tuple) noexcept {
    static_assert(std::tuple_size_v<std::decay_t<NestedTuple>> > 0, "empty tuple");
    return having_first_of_type_impl<T, 0>(std::forward<NestedTuple>(nested_tuple));
}

直播:http://coliru.stacked-crooked.com/a/aa1637939a5d7d7c

我不是 100% 有信心我已经正确地完成了价值类别等方面的所有事情,而且很可能有更好的方法来解决这个问题,但这是我开始时要做的事情。

【讨论】:

  • 感谢constexpr if 提供的解决方案 - 这是我必须学习的内容!但是通过使用的代码逻辑,n314159 的答案更接近我最初的问题。
  • @atlascoder 是的,不用担心 :) 我只是发现函数比模板元程序更容易编写和理解,所以这是我首先跳到的,但不幸的是只有从 C++17 开始才真正可行一般。
  • 是的,我读到了if constexpr,并同意它更容易一些,因为没有使用可变参数模板
猜你喜欢
  • 2023-01-18
  • 1970-01-01
  • 1970-01-01
  • 2021-02-04
  • 1970-01-01
  • 1970-01-01
  • 2021-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多