【问题标题】:Extract Parameter Pack提取参数包
【发布时间】:2018-12-09 06:07:17
【问题描述】:

我想编写一个函数,采用T 类型的N 参数。用例是使用初始化列表初始化类中的数组。

Vector<uint8_t, 3> vector {5, 4, 3} 
Vector<uint16_t, 2> vector2 {90, 7}

我不使用std::initalizer 列表的原因是对于我开发的平台,没有可用的标准库。因此我尝试了一些自制的integer_list(类似于std的方法)。

template <uint8_t ... Ints >
class IndexSequence {};

这很好用(Array 也类似于std::array):

template <typename T, uint8_t I>
using ParamPack = T;

template <typename T, uint8_t N, typename I = makeIndexSequence<N>>
class Test;

template <typename T, uint8_t N, uint8_t... I>
class Test<T, N, IndexSequence<I...>> {

public:
 Test(ParamPack<T, I>... v)
 : data {v...} {}
private:
 Array<T,N> data;
};

但我不太喜欢这种从 integerSequece 中提取整数参数包的“技巧”,因为整数参数包现在是类模板的一部分。我实际上更喜欢它只是在构造函数的模板中,甚至没有任何模板。

例如这样的:

Test(ParamPack<T, makeIndexSequence<N>.getParamPack()>... v)

或类似的东西。在我看来,这会更干净。 有什么方法可以从integerSequence 中提取参数包,而类模板中没有参数包?

我尝试对函数模板使用相同的技巧。不幸的是,它不起作用,因为函数不能部分特化。

也许你甚至有一个完全不同的方法来拥有类似std::initializer_list 或特定类型的参数包(我知道只使用可变参数模板并将其转换为 T 的可能性,但我想避免如果有更好的解决方案,请这样做)。

【问题讨论】:

  • 您是否听说过 C++/标准库的“独立”实现,它只包含标准库的零成本部分?其次,作为类的一部分的名称是问题所在;调试?符号长度?随机squick?
  • 我猜这是标准库的一个端口?我的平台是 avr,一个 8 位微控制器。我认为存在一些移植标准的尝试,但它们不是很好。我不想这样的原因是我想使用它的类,实际上也是一个部分专业化。然后我在基类等中也有这个模板参数......这是可能的,但它使代码看起来比它应该的更复杂
  • 如果数组类似于std::array,那么您可以使用聚合初始化。你的类是否建立了任何不变量?
  • 聚合初始化用于创建数组。它基本上只是一个包装器中的 c 数组。但是在我使用它的课程中,它是私有的,所以我想我不能使用聚合初始化。我实现了这样的构造函数: Test(Array&& data); ...所以我可以像这样使用它... Test test {{1,2,3}}; ...但我不喜欢双括号...只是语法糖:)
  • @Leon0402 用 _iner_array 之类的非平凡名称将其公开怎么样。访问控制很容易被绕过。而且您不会是第一个这样做的人:所有std::array 的内部 c 数组是公共成员。

标签: c++


【解决方案1】:

为什么要避免使用可变参数模板?它们是解决这类问题的正确方法。

没有简单的方法可以从静态值N 生成动态数量的参数。所有解决方案都涉及对某种可变参数输入的专门化(std::index_sequence 的作用完全相同,它会生成可变参数模板参数)。

我认为最优雅的方法是使用可变参数模板并使用static_assert 保护输入。

template <typename T, size_t N>
class Test {
 public:
  template <typename... Ts>
  Test(Ts&&... v) : data{std::forward<Ts>(v)...} {
    static_assert(sizeof...(Ts) == N, "invalid number of arguments");
    static_assert((std::is_same_v<T, Ts> && ...), "wrong type");
  }

 private:
  std::array<T, N> data;
};

请注意,我在此示例中使用 std::array。当您传递无效类型或无效数量的参数时,它无法编译。

Test<int, 2> a{1, 2}; // compiles
Test<int, 2> b{1}; // invalid number of arguments
Test<int, 2> c{1, 2, 3}; // invalid number of arguments
Test<int, 2> d{1, 2.f}; // wrong type

【讨论】:

  • 很抱歉,我的观点可能不够清楚。我以前使用过这种方法(只是没有 std,因为它不可用)......但通常 1 或 2 被解释为整数......当我使用 Test a {1,2} ...有一个从 int 转换为 uint8_t ...但实际上这些数字可以直接解释为 uint8_t。我只是想知道没有演员表是否有任何解决方案,但比我的更好。
  • 那不正确。输入{1, 2} 将在编译时检查,没有强制转换。尝试传递12345679int-lvalue,它不会编译,您必须执行显式转换。这同样适用于仅接受 uint8_ts 的自定义构造函数。当您传递需要缩小的类型时,您必须执行显式转换(因此,从技术上讲,两种解决方案是相同的)。
  • 我不想要隐式转换也不想要显式转换。我只是希望值 1 或 2 直接属于 T 类型。想象一下构造函数 Test(T param1, T param2) ...如果我再写 Test test {1,2} ... 1并且 2 将被解释为 uint8_t 没有任何演员(据我所知)。对于您的解决方案或任何带有 static_cast 的解决方案,1 或 2 将首先是一个 int(我想在我的平台上为 int16_t),然后将在 uint8_t 中强制转换(隐式或显式地使用 static_cast)。所以我需要这个带有类型 T 参数的构造函数,但带有 N 参数。
  • 我知道这不是一个真正的问题,因为 static_cast 实际上完全没问题。我仍然很好奇是否有任何解决方案可以拥有特定类型的 std::initializer_list 或可变参数模板......或者我在问题中描述的解决方案。虽然它并不真正相关,但这个问题向我展示了很多新事物(例如关于索引序列、部分专业化、一些模板技巧等)。所以我对其他可能知道的解决方案很感兴趣。
  • 我只是想指出,没有执行隐式缩小的场景(允许任何其他输入)。当然,你必须传递正确的类型(6 不是uint8_t)。我了解我的解决方案不能解决您的问题。
猜你喜欢
  • 2017-02-13
  • 2015-09-04
  • 1970-01-01
  • 2021-05-06
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多