【问题标题】:variadic template of a specific type特定类型的可变参数模板
【发布时间】:2012-11-18 03:31:27
【问题描述】:

我想要一个只接受无符号整数的可变参数模板。 但是,我无法让以下工作。

struct Array
{
    template <typename... Sizes> // this works
    // template <unsigned... Sizes> -- this does not work (GCC 4.7.2)
    Array(Sizes... sizes)
    {
        // This causes narrowing conversion warning if signed int is supplied.
        unsigned args[] = { sizes... };
        // ...snipped...
    }
};

int main()
{
    Array arr(1, 1);
}

任何帮助表示赞赏。

编辑:如果您想知道,我正在尝试使用可变参数模板来复制以下内容。

struct Array
{
    Array(unsigned size1) { ... }
    Array(unsigned size1, unsigned size2) { ... }
    Array(unsigned size1, unsigned size2, unsigned size3) { ... }
    // ...
    Array(unsigned size1, unsigned size2, ..., unsigned sizeN) { ... }
};

【问题讨论】:

  • 请举例说明。
  • 而这个 arr(1, 1) 会是一个二维数组吗?还是一个初始值为 {1, 1} 的数组?
  • 不幸的是,没有语法可以接受可变数量的相同类型的参数。 ://
  • @OlafDietsche:二维数组。
  • @Xeo:我希望复制手动扩展的 Array c'tor,其中包含重载数量的无符号参数(我之前最多有 10 个)。

标签: c++ gcc c++11 variadic-templates


【解决方案1】:

我不确定你为什么期望它会起作用。 Clang 告诉我构造函数声明中的错误是unknown type name 'Sizes'。这是意料之中的,因为Sizes 不是一个类型(或者更确切地说,一个类型的模板包),它是一个值的模板包。

目前还不清楚您究竟想在这里做什么。如果将整数值作为模板参数传入,构造函数参数应该是什么?


更新:有了新代码,您只需要static_cast&lt;unsigned&gt;()。

struct Array
{
    template <typename... Sizes> // this works
    Array(Sizes... sizes)
    {
        unsigned args[] = { static_cast<unsigned>(sizes)... };
        // ...snipped...
    }
};

【讨论】:

  • 我正在尝试摆脱缩小转换警告(从 int 到 unsigned),而不强制 Array 的用户指定 Array arr(1u, 1u)。
  • @ZachSaw:在模板包中为每个参数复制后具有... 的整个“事物”。这一切都感觉有点神奇。
【解决方案2】:

如果你想接受 dynamic 参数必须都是整数,你需要一个普通的 typename 模板,但检查所有类型是否(可转换为)无符号整数:

#include <type_traits>

struct Array
{
    template <typename ...Args>
    explicit Array(Args ...args,
        typename std::enable_if<all_int<Args...>::value>::type * = nullptr);

    // ...
};

现在你只需要 trait:

template <typename...> struct all_int;

template <> struct all_int<> : std::true_type { };

template <typename T, typename ...Rest> struct all_int<T, Rest...>
: std::integral_constant<bool,
       std::is_convertible<T, unsigned int>::value && all_int<Rest>::value>
{ }

如果您希望类型严格,也可以使用is_same 代替is_convertible。

另一种选择是完全放弃可变参数模板并通过接受单个 std::initializer_list&lt;unsigned int&gt; 使您的类列表可初始化,这提供了相当好的数字安全性(例如,禁止缩小转换)。

【讨论】:

  • 这个问题是如果你传递一个int类型而不是将它转换成unsigned,它会出错。
  • 列表初始化是防止缩小转换的原因,这对std::initializer_list 并不特别。此外,OP 基本上想要Array(unsigned... args)。这不是关于他们能够转换为unsigned,而是关于他们已经成为unsigned,因此他不会在ctor 内的列表初始化中缩小转换范围。
  • @Pubby:int 不是可转换为unsigned int 吗?也许is_constructible 是一个更好的特征。
  • @Xeo:是的,但是如果没有初始化列表,你就不能利用列表初始化,不是吗?
  • struct X{ X(unsigned, unsigned){} }; X x{1,2}; // error(好吧,至少当 GCC 正确地将缩小转换作为错误实现时......)
【解决方案3】:

查看initializer list

你可以像这样指定它

struct Array
{
    Array(std::initializer_list<unsigned> sizes)
    {
        for (auto i = sizes.begin(); i != sizes.end(); ++i)
            ...
    }
}

虽然,用法会更改为

Array arr = {1, 1};

【讨论】:

  • 我试图不使用() 破坏现有代码。使用std::initializer_list 需要我将其更改为{}。
  • @ZachSaw 太糟糕了,你无法改变它。无论如何,我在我的答案中添加了用法的变化。
【解决方案4】:

背景故事

当试图或多或少地实现 OP 试图做的事情时,我偶然发现了这个相当古老的问题。我已经实现了类似于@Kerrek SB 的解决方案的东西,并且正在寻找一种“概括”这种行为的方法(通过提供一个谓词作为模板结构,该结构被评估为不必重新实现不同谓词的“递归” )。 然而,在这样做的同时,我意识到新的 CPP20 概念功能以非常优雅的方式解决了这个问题,因此想分享这个解决方案。我没有将概念视为解决方案,因为我在某处读到它们不能以递归方式声明(我发现这不是实际负担,因为我可以参考递归类型特征解决方案)。

解决方案

在此解决方案中,我定义了一个自定义概念,因为我认为 STL 提供的概念不涵盖大多数实际用例,可以在这种情况下使用。只需用正在使用的概念替换“类型名称”,该概念就会应用于所有提供的模板参数。

#include <concepts>
#include <type_traits>

template <typename T> concept unsignedType = std::is_unsigned_v<T>; 

struct Array {
  template <unsignedType... Sizes> Array(Sizes... sizes) {
    unsigned args[] = {sizes...};
  }
};

int main() {
  unsigned k = 12;
  Array arr(k, 1u); 
  //Array arr(33);  // fails as 33 is not unsigned
}

编译

我使用的是 gcc-10,在以前的版本中,概念功能可能不可用,因为此功能是实验性的: g++-10.0 -std=c++2a 文件.cc

【讨论】:

    猜你喜欢
    • 2023-03-22
    • 2011-09-08
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    • 2020-10-27
    相关资源
    最近更新 更多