获取参数包的前N个元素

25
我有以下问题:

I have to following problem:

template< size_t... N_i >
class A
{
  // ...
};

template< size_t N, size_t... N_i >
A</* first N elements of N_i...*/> foo()
{
  A</* first N elements of N_i...*/> a;

  // ...

  return a;
}

int main()
{
  A<1,2> res = foo<2, 1,2,3,4>();

  return 0;
}

这里,我希望foo的返回类型为A</* first N size_t of N_i...*/>,即具有模板参数N_i的前N个元素的class A

是否有人知道如何实现?


大量使用递归。 - Lightness Races in Orbit
谢谢。难道没有“优雅”的解决方案吗? - abraham_hilbert
29
你正在进行 C++ 模板元编程,并且希望得到一种“优雅”的解决方案? - Lightness Races in Orbit
3
有时候这并不是不可能的 ;) - krzaq
2
@krzaq 可能这次也不是不可能的。;-) - skypjack
6个回答

17

以下是我脑海中想到的最短解决方案(使用两行来定义别名)。
这是一个基于OP发布的代码的最小工作示例:

#include<functional>
#include<cstddef>
#include<utility>
#include<tuple>

template<std::size_t... V>
class A {};

template<std::size_t... V, std::size_t... I>
constexpr auto func(std::index_sequence<I...>) {
    return A<std::get<I>(std::make_tuple(V...))...>{};
}

template<std::size_t N, std::size_t... V>
constexpr auto func() {
    return func<V...>(std::make_index_sequence<N>{});
}

template<std::size_t N, std::size_t... V>
using my_a = decltype(func<N, V...>());

int main() {
    A<1,2> res1 = func<2, 1, 2, 3, 4>();
    // Or even better...
    decltype(func<2, 1, 2, 3, 4>()) res2{};
    // Or even better...
    my_a<2, 1, 2, 3, 4> res3{};
}

3
иҝҷжҳҜmake_tupleе’Ңgetзҡ„constexprзү№жҖ§йқһеёёеҘҪзҡ„еә”з”ЁгҖӮ - krzaq
@krzaq 谢谢。希望这能帮到楼主。 - skypjack

10

这是对@skypjack的答案进行微小变化的一种方式,避免使用元组:

template <size_t... N_i,size_t... M_i>
auto foo2(std::index_sequence<M_i...>)
{
    constexpr size_t values[] = {N_i...};
    return A<values[M_i]...>();
}

template <size_t N,size_t... N_i>
auto foo()
{
    return foo2<N_i...>(std::make_index_sequence<N>());
}

3
你在微小的变化中错过了一些东西。目前这个表达式不能再用于常量表达式。你应该在函数前添加constexpr关键字。如果你决定复制一个答案,请确保将其复制完整。;-) - skypjack

9

最直接的子问题在类型列表领域:

template <class... Ts>
struct typelist {
    using type = typelist;
    static constexpr std::size_t size = sizeof...(Ts);
};

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

template <std::size_t N, class TL>
struct head_n {
    using type = ???;
};

现在,head_n只是简单递归的问题——从一个空列表开始,将一个元素从一个列表移动到另一个列表N次。
template <std::size_t N, class R, class TL>
struct head_n_impl;

// have at least one to pop from and need at least one more, so just 
// move it over
template <std::size_t N, class... Ts, class U, class... Us>
struct head_n_impl<N, typelist<Ts...>, typelist<U, Us...>>
: head_n_impl<N-1, typelist<Ts..., U>, typelist<Us...>>
{ };

// we have two base cases for 0 because we need to be more specialized
// than the previous case regardless of if we have any elements in the list
// left or not
template <class... Ts, class... Us>
struct head_n_impl<0, typelist<Ts...>, typelist<Us...>>
: tag<typelist<Ts...>>
{ };

template <class... Ts, class U, class... Us>
struct head_n_impl<0, typelist<Ts...>, typelist<U, Us...>>
: tag<typelist<Ts...>>
{ };

template <std::size_t N, class TL>
using head_n = typename head_n_impl<N, typelist<>, TL>::type;

从这个问题到你的具体问题,我将留给读者自己思考。


另一种方法是通过连接。将typelist<Ts...>的每个元素转换为typelist<T>typelist<>,然后将它们全部连接在一起。concat很简单:

template <class... Ts>
struct concat { };

template <class TL>
struct concat<TL>
: tag<TL>
{ };

template <class... As, class... Bs, class... Rest>
struct concat<typelist<As...>, typelist<Bs...>, Rest...>
: concat<typelist<As..., Bs...>, Rest...>
{ };

然后我们可以这样做:

template <std::size_t N, class TL, class = std::make_index_sequence<TL::size>>
struct head_n;

template <std::size_t N, class... Ts, std::size_t... Is>
struct head_n<N, typelist<Ts...>, std::index_sequence<Is...>>
: concat<
        std::conditional_t<(Is < N), typelist<Ts>, typelist<>>...
        >
{ };

template <std::size_t N, class TL>
using head_n_t = typename head_n<N, TL>::type;

这种方法的优点在于,可以在C++17中使用折叠表达式代替concat,只需提供适当的operator+
template <class... As, class... Bs>
constexpr typelist<As..., Bs...> operator+(typelist<As...>, typelist<Bs...> ) {
    return {};
}

这使得:

template <std::size_t N, class... Ts, std::size_t... Is>
struct head_n<N, typelist<Ts...>, std::index_sequence<Is...>>
{
    using type = decltype(
        (std::conditional_t<(Is < N), typelist<Ts>, typelist<>>{} + ... + typelist<>{})
        );
};        

4
这在使用Boost.Hana时相当简单:
namespace hana = boost::hana;

template<size_t... vals>
auto make_a(hana::tuple<hana::integral_constant<size_t, vals>...>)
{
    return A<vals...>{};
}

template<size_t N, size_t... vals>
auto foo(){
    constexpr auto front = hana::take_front(
        hana::tuple_c<size_t, vals...>,
        hana::integral_c<size_t,N>
    );
    return detail::make_a(front);
}

现场演示


3
您还可以使用可变泛型lambda表达式和可重用的帮助结构来进行编译时迭代:
#include <utility>
#include <tuple>

template <std::size_t N, class = std::make_index_sequence<N>>
struct iterate;

template <std::size_t N, std::size_t... Is>
struct iterate<N, std::index_sequence<Is...>> {
   template <class Lambda>
   auto operator()(Lambda lambda) {
      return lambda(std::integral_constant<std::size_t, Is>{}...);
   }
};

template <size_t... Is>
struct A { };

template <size_t N, size_t... Is>
auto foo() {
   return iterate<N>{}([](auto... ps){
      using type = std::tuple<std::integral_constant<std::size_t, Is>...>;
      return A<std::tuple_element_t<ps, type>{}...>{};
   });
}

int main() {
   decltype(foo<3, 1, 2, 3, 4>()) a; // == A<1, 2, 3> a;
}

顺便说一句,我认为这个解决方案并不棘手,而且可能非常可扩展 :) - W.F.
不要误会,我喜欢你将默认参数、局部特化和Lambda函数结合起来的方法。只是我猜对于特定情况来说可能太过棘手了。我属于这样一派人,如果你可以在一行中完成它,就不应该浪费下面的那一行。仅此而已。无论如何,解决方案还是很棒的。;-) - skypjack
@skypjack 好像是个不错的聚会;)但我可能也会被误解...一旦创建了结构体“iterate”,您可以在需要编译时迭代N个元素的任何地方使用它。这将使您以前需要创建两个函数的其他地方--一个带有std :: make_index_sequence <N>和其带有std :: index_sequence <Is ...>的“impl”版本--变得不必要。现在你明白我的意思了吗? :) - W.F.
1
我在两三个评论之前就明白了。:-D ... 我知道你的意思,但我认为你仍然可以用更紧凑的形式来写。无论如何,我喜欢这个因为它是一个大脑健身操。;-) - skypjack
1
@skypjack 哈哈,脑力健身,我得记住这个 :) 你用“如果可以在一行中完成,就不应该浪费下面的行”的评论把我搞糊涂了 :) 老实说,我也认为你的解决方案可能是最适合 OP 的,但也许将来有人会发现代码更适合他或她的特定问题 :) - W.F.
2
你可以将 decltype(ps)::value 简写为 ps。而整个 tuple_element 的内容则可以简化为 A<std::tuple_element_t<ps, type>{}...>{} - Barry

2

不幸的是,这种方法需要定义额外的Helper类型。

template< size_t... N_i >
class A
{
};

template <size_t... N_i>
struct Helper;

template <size_t... N_i>
struct Helper<0, N_i...>
{
    typedef A<> type;
};

template <size_t N0, size_t... N_i>
struct Helper<1, N0, N_i...>
{
    typedef A<N0> type;
};

template <size_t N0, size_t N1, size_t... N_i>
struct Helper<2, N0, N1, N_i...>
{
    typedef A<N0, N1> type;
};

template< size_t N, size_t... N_i >
typename Helper<N, N_i...>::type foo()
{
  typename Helper<N, N_i...>::type a;
  return a;
}

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