将可变参数包缩短为 N 种

发布于 2025-01-10 20:06:33 字数 763 浏览 2 评论 0原文

我想编写一个大小为 N (> 0) 和可变数量参数 (>= N) 的类。它应该有一个接受 N 个参数的构造函数和一个具有相同类型的成员 std::tuple

template <size_t N, typename... Args>
struct Example {
  // A constructor taking N parameters of type Args[N], initializing the member tuple 
  // (e.g. param 1 has type Args[0], param 2 has type Args[1], ..., 
  // param N has type Args[N-1])
  // A tuple with N elements, each corresponding to Args[N]
  // (e.g. std::tuple<Args[0], ..., Args[N-1]>)

  //For instance Example<3, int, float, int, bool> should result in
  constexpr Example(int a, float b, int c): t(a, b, c) {}
  std::tuple<int, float, int> t;
}

一般来说:这可能吗?如果没有,是否有可行的替代方案?为什么这有效/无效?我正在使用 C++20。

I would like to write a class that takes a size N (> 0) and a variable number of arguments (>= N). It should have a constructor that takes N arguments and a member std::tuple which has the same type:

template <size_t N, typename... Args>
struct Example {
  // A constructor taking N parameters of type Args[N], initializing the member tuple 
  // (e.g. param 1 has type Args[0], param 2 has type Args[1], ..., 
  // param N has type Args[N-1])
  // A tuple with N elements, each corresponding to Args[N]
  // (e.g. std::tuple<Args[0], ..., Args[N-1]>)

  //For instance Example<3, int, float, int, bool> should result in
  constexpr Example(int a, float b, int c): t(a, b, c) {}
  std::tuple<int, float, int> t;
}

In general: Is this possible? If not are there viable alternatives? Why does/ doesn't this work? I'm using C++20.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

忆悲凉 2025-01-17 20:06:33

就我对这个问题的理解而言,它似乎只是在问如何从参数生成元组。其中,使用 Boost.Mp11, 是一个简短的单行代码(一如既往):

template <size_t N, typename... Args>
using Example = mp_take_c<N, std::tuple<Args...>>;

而不是 Example<3, int, float, int, bool> 是具有成员的某种类型tuple 带有一个构造函数,它实际上tuple

如果由于某种原因,您特别需要一个成员元组和指定的构造函数,我们可以很容易地做到:

template <typename... Ts>
struct ExampleImpl {
    std::tuple<Ts...> t;
    constexpr ExampleImpl(Ts... ts) : t(ts...) { }
};

template <size_t N, typename... Args>
using Example = mp_take_c<N, ExampleImpl<Args...>>;

To the extent I understand the question, it simply seems to be asking how to produce a tuple from arguments. Which, using Boost.Mp11, is a short one-liner (as always):

template <size_t N, typename... Args>
using Example = mp_take_c<N, std::tuple<Args...>>;

Rather than Example<3, int, float, int, bool> being some type that has a member tuple<int, float, int> with one constructor, it actually is tuple<int, float int>.

If you, for some reason, specifically need exactly a member tuple and exactly the constructor specified, we can do easily enough:

template <typename... Ts>
struct ExampleImpl {
    std::tuple<Ts...> t;
    constexpr ExampleImpl(Ts... ts) : t(ts...) { }
};

template <size_t N, typename... Args>
using Example = mp_take_c<N, ExampleImpl<Args...>>;
墨洒年华 2025-01-17 20:06:33

这是使用 C++20 的实现:

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

template <class... Ts> struct typelist;

template <size_t N, class, class> struct take;

// Takes N elements of the given type pack
template <size_t N, class... Ts>
using take_t = typename take<N, typelist<>, typelist<Ts...>>::type;

template <class Take, class Drop>
struct take<0, Take, Drop>  {
    using type = Take;
};

template <size_t N, class T, class... Ts, class... Us>
requires(N > 0)
struct take<N, typelist<Us...>, typelist<T, Ts...>> {
    using type = typename take<N - 1, typelist<Us..., T>, typelist<Ts...>>::type;
};

template <class Ts> 
struct make_ctor;

template <class... Ts>
struct make_ctor<typelist<Ts...>> {
    constexpr make_ctor(Ts... ts) : tuple(ts...) {}

    std::tuple<Ts...> tuple;
};

template <size_t N, class... Args>
struct Example : 
    make_ctor<take_t<N, Args...>> {
    using make_ctor<take_t<N, Args...>>::make_ctor;
};

int main() {
    Example<3, int, float, int, bool> e(3, 3.14, 4);
}

我们在这里所做的是,首先使用 take 元函数删除额外的模板参数。然而,由于类型包在 C++ 中不是一流的,因此我们只能采用所需类型的 typelist 。为了从给定的 typelist 创建元组 + 构造函数,我们有一个名为 make_ctor 的辅助类型。通过继承它的构造函数,我们就得到了想要的API。

对于生产,请参阅巴里的答案并使用现有的库进行元编程部分。

Here is an implementation using C++20:

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

template <class... Ts> struct typelist;

template <size_t N, class, class> struct take;

// Takes N elements of the given type pack
template <size_t N, class... Ts>
using take_t = typename take<N, typelist<>, typelist<Ts...>>::type;

template <class Take, class Drop>
struct take<0, Take, Drop>  {
    using type = Take;
};

template <size_t N, class T, class... Ts, class... Us>
requires(N > 0)
struct take<N, typelist<Us...>, typelist<T, Ts...>> {
    using type = typename take<N - 1, typelist<Us..., T>, typelist<Ts...>>::type;
};

template <class Ts> 
struct make_ctor;

template <class... Ts>
struct make_ctor<typelist<Ts...>> {
    constexpr make_ctor(Ts... ts) : tuple(ts...) {}

    std::tuple<Ts...> tuple;
};

template <size_t N, class... Args>
struct Example : 
    make_ctor<take_t<N, Args...>> {
    using make_ctor<take_t<N, Args...>>::make_ctor;
};

int main() {
    Example<3, int, float, int, bool> e(3, 3.14, 4);
}

What we do here is, first we drop the extra template arguments using the take meta-function. However, since type packs are not first class in C++, we only get to take a typelist of our desired types. To create a tuple + a constructor from a given typelist, we have a helper type called make_ctor. By inheriting its constructor, we get the desired API.

For production, see Barry's answer and use an existing library for the metaprogramming part.

邮友 2025-01-17 20:06:33

使用 std::tuple_element 和辅助基类,在我看来,你可以编写如下内容(C++14 就足够了)

#include <tuple>
#include <string>

template <typename ...>
struct ExHelper;

template <std::size_t ... Is, typename Tpl>
struct ExHelper<std::index_sequence<Is...>, Tpl>
{
  using t_type = std::tuple<typename std::tuple_element<Is, Tpl>::type...>;

  t_type  t;

  constexpr ExHelper(typename std::tuple_element<Is, Tpl>::type ... args)
    : t{std::move(args)...}
  { }
};

template <std::size_t N, typename... Args>
struct Example : public ExHelper<std::make_index_sequence<N>,
                                 std::tuple<Args...>> {
  static_assert( N <= sizeof...(Args), "!" );

  using ExHelper<std::make_index_sequence<N>,
                 std::tuple<Args...>>::ExHelper;
};

int main ()
{
  Example<3, int, float, int, std::string>  e0{1, 2.0f, 3};

  static_assert( std::is_same<decltype(e0)::t_type,
                              std::tuple<int, float, int>>::value, "!" );
}

Using std::tuple_element and an helper base class, seems to me that you can write something as follows (C++14 is enough)

#include <tuple>
#include <string>

template <typename ...>
struct ExHelper;

template <std::size_t ... Is, typename Tpl>
struct ExHelper<std::index_sequence<Is...>, Tpl>
{
  using t_type = std::tuple<typename std::tuple_element<Is, Tpl>::type...>;

  t_type  t;

  constexpr ExHelper(typename std::tuple_element<Is, Tpl>::type ... args)
    : t{std::move(args)...}
  { }
};

template <std::size_t N, typename... Args>
struct Example : public ExHelper<std::make_index_sequence<N>,
                                 std::tuple<Args...>> {
  static_assert( N <= sizeof...(Args), "!" );

  using ExHelper<std::make_index_sequence<N>,
                 std::tuple<Args...>>::ExHelper;
};

int main ()
{
  Example<3, int, float, int, std::string>  e0{1, 2.0f, 3};

  static_assert( std::is_same<decltype(e0)::t_type,
                              std::tuple<int, float, int>>::value, "!" );
}
何以心动 2025-01-17 20:06:33

您可以使用 make_index_sequence 来帮助从 Args... 中提取第一个 N

例子:

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

// Create a tuple from the sizeof...(I) first elements in the supplied tuple:
template <class... Ts, size_t... I>
constexpr auto extract_from_tuple(std::tuple<Ts...>&& tmp, std::index_sequence<I...>) {
    static_assert(sizeof...(Ts) >= sizeof...(I));
    // make a new tuple:
    return std::make_tuple(std::move(std::get<I>(tmp))...);
}

template <size_t N, class... Args>
struct Example {
    template<class... Ts, std::enable_if_t<sizeof...(Ts)==N, int> = 0>
    constexpr Example(Ts&&... args)
        : t{extract_from_tuple(std::make_tuple(std::forward<Ts>(args)...), 
            std::make_index_sequence<N>{})} {}

    // using the same extract_from_tuple function to deduce the type of `t`
    decltype(extract_from_tuple(std::make_tuple(std::declval<Args>()...),
                                std::make_index_sequence<N>{})) t;
};

int main() {
    Example<3, int, float, double, bool> foo(1, 2.f, 3.);

    static_assert(std::is_same_v<decltype(foo.t), std::tuple<int, float, double>>);
}

You could make_index_sequence<N> to help with extracting the first N from Args....

Example:

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

// Create a tuple from the sizeof...(I) first elements in the supplied tuple:
template <class... Ts, size_t... I>
constexpr auto extract_from_tuple(std::tuple<Ts...>&& tmp, std::index_sequence<I...>) {
    static_assert(sizeof...(Ts) >= sizeof...(I));
    // make a new tuple:
    return std::make_tuple(std::move(std::get<I>(tmp))...);
}

template <size_t N, class... Args>
struct Example {
    template<class... Ts, std::enable_if_t<sizeof...(Ts)==N, int> = 0>
    constexpr Example(Ts&&... args)
        : t{extract_from_tuple(std::make_tuple(std::forward<Ts>(args)...), 
            std::make_index_sequence<N>{})} {}

    // using the same extract_from_tuple function to deduce the type of `t`
    decltype(extract_from_tuple(std::make_tuple(std::declval<Args>()...),
                                std::make_index_sequence<N>{})) t;
};

int main() {
    Example<3, int, float, double, bool> foo(1, 2.f, 3.);

    static_assert(std::is_same_v<decltype(foo.t), std::tuple<int, float, double>>);
}
尬尬 2025-01-17 20:06:33

有所谓的模板推导指南。下面的代码中用注释“//模板推导指南”进行了标记。

本指南展示了如何从构造函数的参数推断结构的模板类型。

现在您可以使用任何类型调用构造函数(就像我在 main() 中所做的那样)。

在线尝试!

#include <tuple>

template <std::size_t N, typename ... Args>
struct Example {
    constexpr Example(Args && ... args)
        : t(std::forward<Args>(args)...) {}
    std::tuple<Args...> t;
};

// Template Deduction Guide
template <typename ... Args>
Example(Args && ...) -> Example<sizeof...(Args), Args...>;

#include <iostream>
#include <iomanip>

int main() {
    Example example(int{3}, float{5.7}, bool{true});
    std::cout
        << std::boolalpha << "size "
        << std::tuple_size_v<decltype(example.t)> << " tuple "
        << std::get<0>(example.t) << " " << std::get<1>(example.t)
        << " " << std::get<2>(example.t) << std::endl;
}

输出:

size 3 tuple 3 5.7 true

There are so-called Template Deduction Guides. It is marked in code below with comment "// Template Deduction Guide".

This guide shows how to deduce template types of a structure from arguments of constructor.

Now you can call constructor (as I did in main()) with any types.

Try it online!

#include <tuple>

template <std::size_t N, typename ... Args>
struct Example {
    constexpr Example(Args && ... args)
        : t(std::forward<Args>(args)...) {}
    std::tuple<Args...> t;
};

// Template Deduction Guide
template <typename ... Args>
Example(Args && ...) -> Example<sizeof...(Args), Args...>;

#include <iostream>
#include <iomanip>

int main() {
    Example example(int{3}, float{5.7}, bool{true});
    std::cout
        << std::boolalpha << "size "
        << std::tuple_size_v<decltype(example.t)> << " tuple "
        << std::get<0>(example.t) << " " << std::get<1>(example.t)
        << " " << std::get<2>(example.t) << std::endl;
}

Output:

size 3 tuple 3 5.7 true
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文