选择可变参数模板的最后一个参数的有效方法

Khu*_*dov 13 c++ variadic-templates c++11

我知道如何选择可变参数模板的第一个参数:

template< class...Args> struct select_first;
template< class A, class ...Args> struct select_first<A,Args...>{  using type = A;};
Run Code Online (Sandbox Code Playgroud)

这很简单.但是,select_last不相似:

template< class ...Args> struct select_last;
template< class A> struct select_last<A> { using type = A; };
template< class A, class Args...> struct select_last<A,Args...>{ 
        using type = typename select_last<Args...>::type;
};
Run Code Online (Sandbox Code Playgroud)

该解决方案需要深度递归模板即时.我尝试用以下方法解决这个问题:

template< class A, class Args...>
struct select_last< Args ... , A>{  using type = A; }; // but it's not compiled.
Run Code Online (Sandbox Code Playgroud)

问:存在更有效的方法来选择可变参数模板的最后一个参数吗?

Pas*_* By 14

使用C++ 17,最干净的方法是

template<typename T>
struct tag
{
    using type = T;
};

template<typename... Ts>
struct select_last
{
    using type = typename decltype((tag<Ts>{}, ...))::type;
};
Run Code Online (Sandbox Code Playgroud)

具有O(1)实例化深度.

  • @marco6, `std::declval&lt;T&gt;()` 返回一个引用类型,您必须删除该引用。但是如果 `T` 本身就是一个引用类型,那么你就会遇到问题。在 C++20 中,我们将为“tag”使用“std::type_identity”。 (4认同)
  • 是否有理由不使用 `std::declval&lt;T&gt;()` 而不是 `tag&lt;T&gt;()` 例如 `template&lt;typename... Ts&gt; struct select_last { using type = decltype((std:: declval&lt;Ts&gt;{}, ...)); }; `? (2认同)

dyp*_*dyp 8

与上次相同的方法,O(logN)实例化深度.仅使用一个重载,因此它应该消耗更少的资源.

警告:它当前从元组类型中删除引用. 注意:从中删除了引用pack::declval.我认为它仍适用于所有情况.

O(log(N))实例化中的索引技巧,由Xeo; 修改为使用std::size_t而不是unsigned

    #include <cstddef>

    // using aliases for cleaner syntax
    template<class T> using Invoke = typename T::type;

    template<std::size_t...> struct seq{ using type = seq; };

    template<class S1, class S2> struct concat;

    template<std::size_t... I1, std::size_t... I2>
    struct concat<seq<I1...>, seq<I2...>>
      : seq<I1..., (sizeof...(I1)+I2)...>{};

    template<class S1, class S2>
    using Concat = Invoke<concat<S1, S2>>;

    template<std::size_t N> struct gen_seq;
    template<std::size_t N> using GenSeq = Invoke<gen_seq<N>>;

    template<std::size_t N>
    struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};

    template<> struct gen_seq<0> : seq<>{};
    template<> struct gen_seq<1> : seq<0>{};
Run Code Online (Sandbox Code Playgroud)

今天,我意识到有一个不同的,更简单的,可能更快的(编译时)解决方案来获得第n类型的元组(基本上是一个实现std::tuple_element).即使它是另一个问题的直接解决方案,我也会在这里发布它是为了完整性.

namespace detail
{
    template<std::size_t>
    struct Any
    {
        template<class T> Any(T&&) {}
    };

    template<typename T>
    struct wrapper {};

    template<std::size_t... Is>
    struct get_nth_helper
    {
        template<typename T>
        static T deduce(Any<Is>..., wrapper<T>, ...);
    };

    template<std::size_t... Is, typename... Ts>
    auto deduce_seq(seq<Is...>, wrapper<Ts>... pp)
    -> decltype( get_nth_helper<Is...>::deduce(pp...) );
}

#include <tuple>

template<std::size_t n, class Tuple>
struct tuple_element;

template<std::size_t n, class... Ts>
struct tuple_element<n, std::tuple<Ts...>>
{
    using type = decltype( detail::deduce_seq(gen_seq<n>{},
                                              detail::wrapper<Ts>()...) );
};
Run Code Online (Sandbox Code Playgroud)

帮助最后一个元素:

template<typename Tuple>
struct tuple_last_element;

template<typename... Ts>
struct tuple_last_element<std::tuple<Ts...>>
{
    using type = typename tuple_element<sizeof...(Ts)-1,
                                        std::tuple<Ts...>> :: type;
};
Run Code Online (Sandbox Code Playgroud)

用法示例:

#include <iostream>
#include <type_traits>
int main()
{
    std::tuple<int, bool, char const&> t{42, true, 'c'};

    tuple_last_element<decltype(t)>::type x = 'c'; // it's a reference

    static_assert(std::is_same<decltype(x), char const&>{}, "!");
}
Run Code Online (Sandbox Code Playgroud)

原始版本:

#include <tuple>
#include <type_traits>

namespace detail
{
    template<typename Seq, typename... TT>
    struct get_last_helper;

    template<std::size_t... II, typename... TT>
    struct get_last_helper< seq<II...>, TT... >
    {
        template<std::size_t I, std::size_t L, typename T>
        struct pack {};
        template<typename T, std::size_t L>
        struct pack<L, L, T>
        {
            T declval();
        };

        // this needs simplification..
        template<typename... TTpacked>
        struct exp : TTpacked...
        {
            static auto declval_helper()
                -> decltype(std::declval<exp>().declval());
            using type = decltype(declval_helper());
        };

        using type = typename exp<pack<II, sizeof...(TT)-1, TT>...>::type;
    };
}

template< typename Tuple >
struct get_last;

template< typename... TT >
struct get_last<std::tuple<TT...>>
{
    template<std::size_t... II>
    static seq<II...> helper(seq<II...>);
    using seq_t = decltype(helper(gen_seq<sizeof...(TT)>()));

    using type = typename detail::get_last_helper<seq_t, TT...>::type;
};


int main()
{
    using test_type = std::tuple<int, double, bool, char>;

    static_assert(std::is_same<char, get_last<test_type>::type>::value, "!");
    // fails:
    static_assert(std::is_same<int, get_last<test_type>::type>::value, "!");
}
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 5

如果您愿意从类型列表中盲目地去除引用(这种情况很常见:要么您知道它们是引用,要么您不在乎),您可以使用std. 基本上将数据填充到tupleor 中tie,然后用于std::get<sizeof...(X)-1>( tuple or tie )提取最后一个元素。

您可以在纯类型上下文中使用std::declval< std::tuple<Args...> >()and执行此操作decltype,并且可能使用std::remove_reference

例如,假设您有一组可变参数,并且您想返回最后一个参数而忽略其余参数:

#define RETURNS(x) ->decltype(x) { return (x); }

template<typename ...Args>
auto get_last( Args&&... args )
  RETURNS( std::get< sizeof...(Args)-1 >( std::tie(std::forward<Args>(args)...) ) )
Run Code Online (Sandbox Code Playgroud)

然后我们可以在另一个函数中使用它:

template<typename ...Args>
void foo( Args&&... args ) {
  auto&& last = get_last(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)


use*_*370 5

以下是另一种精益的 C++17 方法,它也使用折叠表达式;但通过使用避免了临时类代理std::enable_if

template <typename ...Ts>
struct select_last
{
  using type = typename decltype((std::enable_if<true,Ts>{}, ...))::type;
};

template <typename ...Ts>
using select_last_t = typename select_last<Ts...>::type;

static_assert(std::is_same_v<char, select_last_t<int,double,char>>);
Run Code Online (Sandbox Code Playgroud)

在 C++20 中std::type_identity提供了一种更具可读性的方法:

// C++20
template <typename ...Ts>
struct select_last
{
  using type = typename decltype((std::type_identity<Ts>{}, ...))::type;
};
Run Code Online (Sandbox Code Playgroud)

在 C++26 中,P2662 的索引允许我们保存更多字符:

template <typename ...Ts>
Ts...[sizeof...(Ts)-1] select_last();

static_assert(std::is_same_v<char, decltype(select_last<int,double,char>())>);
Run Code Online (Sandbox Code Playgroud)


jro*_*rok -3

一个相当愚蠢的方法是编写一个帮助器类并专门针对每个数量的参数(最多可达您选择的某些限制)。您可以为此使用预处理器。

template<typename...>
struct select_last_helper;

template<typename T1>
struct select_last_helper<T1> {
    using type = T1;
};

template<typename T1, typename T2>
struct select_last_helper<T1,T2> {
    using type = T2;
};

template<typename T1, typename T2, typename T3>
struct select_last_helper<T1,T2,T3> {
    using type = T3;
};

template<typename... Ts>
struct select_last {
    using type = typename select_last_helper<Ts...>::type;
};
Run Code Online (Sandbox Code Playgroud)

O(1) 模板实例化 :)

  • 嗯,确实,在常见情况下,没有人拥有数千个参数集,只有一小部分参数,所以这可能是一种方法。但是,另一方面,这完全破坏了可变参数模板的威力。这与 Boost::Tuple 或 Loki Typelists 是同样可怕的事情 (2认同)