【问题标题】:get part of std::tuple获取 std::tuple 的一部分
【发布时间】:2011-12-20 00:55:55
【问题描述】:

我有一个未知大小的元组(它是方法的模板参数)

有没有办法得到它的一部分(我需要扔掉它的第一个元素)

例如,我有tuple<int,int,int>(7,12,42)。我要tuple<int,int>(12,42)这里

【问题讨论】:

  • 你有真正的可变参数模板支持(template<class... Args>)吗?另外,您是要复制这些值还是要它们的视图,例如对原始值的引用?
  • @Xeo:我想要更好的参考,但这并不重要。
  • @Xeo:我现在不使用可变参数模板。但我在 g++4.6 上使用 c++0x,我认为它们是受支持的。
  • @RiaD 是的。 gcc.gnu.org/projects/cxx0x.html 或者如果你想要更多的 C++11 特性获取 gcc 4.7,它现在​​相当稳定
  • @Geoffroy,我现在不会用,我只是回答 Xeo 的评论..

标签: c++ g++ c++11 tuples


【解决方案1】:

借助编译时整数列表:

#include <cstdlib>

template <size_t... n>
struct ct_integers_list {
    template <size_t m>
    struct push_back
    {
        typedef ct_integers_list<n..., m> type;
    };
};

template <size_t max>
struct ct_iota_1
{
    typedef typename ct_iota_1<max-1>::type::template push_back<max>::type type;
};

template <>
struct ct_iota_1<0>
{
    typedef ct_integers_list<> type;
};

我们可以简单地通过参数包扩展来构造尾部:

#include <tuple>

template <size_t... indices, typename Tuple>
auto tuple_subset(const Tuple& tpl, ct_integers_list<indices...>)
    -> decltype(std::make_tuple(std::get<indices>(tpl)...))
{
    return std::make_tuple(std::get<indices>(tpl)...);
    // this means:
    //   make_tuple(get<indices[0]>(tpl), get<indices[1]>(tpl), ...)
}

template <typename Head, typename... Tail>
std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& tpl)
{
    return tuple_subset(tpl, typename ct_iota_1<sizeof...(Tail)>::type());
    // this means:
    //   tuple_subset<1, 2, 3, ..., sizeof...(Tail)-1>(tpl, ..)
}

用法:

#include <cstdio>

int main()
{
    auto a = std::make_tuple(1, "hello", 7.9);
    auto b = tuple_tail(a);

    const char* s = nullptr;
    double d = 0.0;
    std::tie(s, d) = b;
    printf("%s %g\n", s, d);
    // prints:   hello 7.9

    return 0;
}

(在 ideone 上:http://ideone.com/Tzv7v;代码在 g++ 4.5 到 4.7 和 clang++ 3.0 中工作)

【讨论】:

  • 我正在寻找“来自工厂”的东西。而且我认为提问者也在要求这样的事情。
  • 请注意,您可以在 C++14 中使用 std::index_sequence 代替 ct_integers_list。
【解决方案2】:

可能有更简单的方法,但这是一个开始。 “tail”函数模板返回一个复制的元组,其中包含除第一个之外的所有原始值。这在 C++0x 模式下使用 GCC 4.6.2 编译。

template<size_t I>
struct assign {
  template<class ResultTuple, class SrcTuple>
  static void x(ResultTuple& t, const SrcTuple& tup) {
    std::get<I - 1>(t) = std::get<I>(tup);
    assign<I - 1>::x(t, tup);
  }
};

template<>
struct assign<1> {
  template<class ResultTuple, class SrcTuple>
  static void x(ResultTuple& t, const SrcTuple& tup) {
    std::get<0>(t) = std::get<1>(tup);
  }
};


template<class Tup> struct tail_helper;

template<class Head, class... Tail>
struct tail_helper<std::tuple<Head, Tail...>> {
  typedef typename std::tuple<Tail...> type;
  static type tail(const std::tuple<Head, Tail...>& tup) {
    type t;
    assign<std::tuple_size<type>::value>::x(t, tup);
    return t;
  }
};

template<class Tup>
typename tail_helper<Tup>::type tail(const Tup& tup) {
  return tail_helper<Tup>::tail(tup);
}

【讨论】:

  • 我只是想指出,由于template&lt;size_t N, typename Tuple_Type&gt; struct tuple_trunc {}; 从未被实例化,它可以写成:template&lt;size_t N, typename Tuple_Type&gt; struct tuple_trunc; 如果有人不小心这样做了会导致编译器错误。
  • @veso - 我在这个答案中没有看到任何“tuple_trunc”。
  • 你的实现需要默认的可构造类型。
【解决方案3】:

对于 C++17,你可以使用std::apply:

template <typename Head, typename... Tail>
std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& t)
{
    return apply([](auto head, auto... tail) {
        return std::make_tuple(tail...)};
    }, t);
}

【讨论】:

    【解决方案4】:

    元组切片操作(也适用于std::array 和std::pair)可以这样定义(需要C++14):

    namespace detail
    {
        template <std::size_t Ofst, class Tuple, std::size_t... I>
        constexpr auto slice_impl(Tuple&& t, std::index_sequence<I...>)
        {
            return std::forward_as_tuple(
                std::get<I + Ofst>(std::forward<Tuple>(t))...);
        }
    }
    
    template <std::size_t I1, std::size_t I2, class Cont>
    constexpr auto tuple_slice(Cont&& t)
    {
        static_assert(I2 >= I1, "invalid slice");
        static_assert(std::tuple_size<std::decay_t<Cont>>::value >= I2, 
            "slice index out of bounds");
    
        return detail::slice_impl<I1>(std::forward<Cont>(t),
            std::make_index_sequence<I2 - I1>{});
    }
    

    可以像这样获得元组t 的任意子集:

    tuple_slice<I1, I2>(t); 
    

    其中[I1, I2) 是子集的独占范围,返回值是对原始元组的引用的元组。如果您想要复制切片操作,请在 slice_impl 中将 forward_as_tuple 替换为 make_tuple。详细阐述可以在我的blog 中找到。

    【讨论】:

    • 不会使用 forward_as_tuple 实际上创建一个元组,该元组引用原始元组的切片而不是原始元组的切片中的项目?
    • @FabioA。我编辑了误导性的最后一段,谢谢
    【解决方案5】:

    我对@9​​87654321@ 做了一些修改,去掉了元组的前 N ​​个参数,并创建了一个只有最后 N 个类型的新元组……这是完整的代码 (注意:如果有人决定 +1 我的回答,也请 +1 Adam 的回答,因为这是此代码的基础,我不希望从他的贡献中拿走任何功劳):

    //create a struct that allows us to create a new tupe-type with the first
    //N types truncated from the front
    
    template<size_t N, typename Tuple_Type>
    struct tuple_trunc {};
    
    template<size_t N, typename Head, typename... Tail>
    struct tuple_trunc<N, std::tuple<Head, Tail...>>
    {
        typedef typename tuple_trunc<N-1, std::tuple<Tail...>>::type type;
    };
    
    template<typename Head, typename... Tail>
    struct tuple_trunc<0, std::tuple<Head, Tail...>>
    {
        typedef std::tuple<Head, Tail...> type;
    };
    
    /*-------Begin Adam's Code-----------
    
    Note the code has been slightly modified ... I didn't see the need for the extra
    variadic templates in the "assign" structure.  Hopefully this doesn't break something
    I didn't forsee
    
    */
    
    template<size_t N, size_t I>
    struct assign 
    {
        template<class ResultTuple, class SrcTuple>
        static void x(ResultTuple& t, const SrcTuple& tup) 
        {
            std::get<I - N>(t) = std::get<I>(tup);  
            assign<N, I - 1>::x(t, tup);  //this offsets the assignment index by N
        }
    };
    
    template<size_t N>
    struct assign<N, 1> 
    {
        template<class ResultTuple, class SrcTuple>
        static void x(ResultTuple& t, const SrcTuple& tup) 
        {
            std::get<0>(t) = std::get<1>(tup);
        }
    };
    
    
    template<size_t TruncSize, class Tup> struct th2;
    
    //modifications to this class change "type" to the new truncated tuple type
    //as well as modifying the template arguments to assign
    
    template<size_t TruncSize, class Head, class... Tail>
    struct th2<TruncSize, std::tuple<Head, Tail...>> 
    {
        typedef typename tuple_trunc<TruncSize, std::tuple<Head, Tail...>>::type type;
    
        static type tail(const std::tuple<Head, Tail...>& tup) 
        {
            type t;
            assign<TruncSize, std::tuple_size<type>::value>::x(t, tup);
            return t;
        }
    };
    
    template<size_t TruncSize, class Tup>
    typename th2<TruncSize, Tup>::type tail(const Tup& tup) 
    {
        return th2<TruncSize, Tup>::tail(tup);
    }
    
    //a small example
    int main()
    {
        std::tuple<double, double, int, double> test(1, 2, 3, 4);
        tuple_trunc<2, std::tuple<double, double, int, double>>::type c = tail<2>(test);
        return 0;
    }
    

    【讨论】:

    • 看起来你对那里的可变参数是正确的,一定是以前的方法遗留下来的。
    【解决方案6】:

    请不要使用!

    • 这是 [最有可能] 未指定的行为。它可能随时停止工作。
    • 此外,存在填充问题的可能性(即它可能适用于int,但可能会因您的类型而失败!)。

    参见 cmets 进行讨论。我留下这个答案仅供参考。


    更简单:

    tuple<int,int,int> origin{7,12,42};
    tuple<int, int> &tail1 = (tuple<int, int>&)origin;
    tuple<int> &tail2 = (tuple<int>&)origin;
    cout << "tail1: {" << get<0>(tail1) << ", " << get<1>(tail1) << "}" << endl;
    cout << "tail2: {" << get<0>(tail2) << "}" << endl;
    

    我明白了:

    tail1: {12, 42}
    tail2: {42}
    

    我不确定这不是未指明的行为。适合我:Fedora 20 和

    ❯ clang --version
    clang version 3.3 (tags/RELEASE_33/final)
    Target: x86_64-redhat-linux-gnu
    Thread model: posix
    ❯ gcc --version
    gcc (GCC) 4.8.2 20131212 (Red Hat 4.8.2-7)
    Copyright (C) 2013 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    

    参考:article on voidnish.wordpress.com/。

    【讨论】:

    • 这是一个漂亮的答案。便携吗?
    • 坦率地说,我不知道。现在看着我的答案并考虑到这一点,我想不幸的是它可能不便携。
    • 这不是可移植的,可能是未定义的行为。 GCC 以一种特殊的方式实现元组,该方式涉及到更高索引的成员被存储在结构的早期。 Clang 则相反。一般来说,你没有任何保证。
    • 在那个时候,它对gcc 和clang 都有效(答案中提供了版本)。但是我不建议这样做。
    • 虽然,我确实喜欢这个并且考虑过它,因为它减少了不必要的tuple 复制,std 中没有为tuple 指定二进制顺序,所以不,这不是可移植的.除非指定 YMMV,否则可能会产生意想不到的错误。我会尝试添加一些static_asserts 以确保您的编译器不会把它搞砸,和/或一些函数包含/排除机制来切换到工作机制。如果您在元组中使用不同的类型,也可能存在一些填充问题,但我不确定。
    【解决方案7】:

    c++17 方式:

    #include <tuple>
    #include <string>
    
    template <size_t __begin, size_t...__indices, typename Tuple>
    auto tuple_slice_impl(Tuple &&tuple, std::index_sequence<__indices...>) {
      return std::make_tuple(std::get<__begin + __indices>(std::forward<Tuple>(tuple))...);
    }
    
    template <size_t __begin, size_t __count, typename Tuple>
    auto tuple_slice(Tuple &&tuple) {
      static_assert(__count > 0, "splicing tuple to 0-length is weird...");
      return tuple_slice_impl<__begin>(std::forward<Tuple>(tuple), std::make_index_sequence<__count>());
    }
    
    template <size_t __begin, size_t __count, typename Tuple>
    using tuple_slice_t = decltype(tuple_slice<__begin, __count>(Tuple{}));
    
    using test_tuple = std::tuple<int, int, bool, nullptr_t, std::string>;
    using sliced_test = tuple_slice_t<2, 2, test_tuple>;
    
    static_assert(std::tuple_size_v<sliced_test> == 2);
    static_assert(std::is_same_v<std::decay_t<decltype(std::get<0>(sliced_test{}))>, bool>);
    static_assert(std::is_same_v<std::decay_t<decltype(std::get<1>(sliced_test{}))>, nullptr_t>);
    
    #include <cassert>
    
    int main() {
      test_tuple tuple {
        -1, 42, true, nullptr, "hello"
      };
      auto spliced = tuple_slice<3, 2>(tuple);
      assert(std::get<0>(spliced) == nullptr);
      assert(std::get<1>(spliced) == std::get<4>(tuple));
      return 0;
    }
    
    

    【讨论】:

      猜你喜欢
      • 2019-06-15
      • 1970-01-01
      • 2015-06-02
      • 1970-01-01
      • 2013-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多