【问题标题】:c++ non-type parameter pack expansionc++ 非类型参数包扩展
【发布时间】:2018-07-30 12:11:17
【问题描述】:

我正在编写由单一类型参数化的模板函数,并且具有可变数量的相同类型(不是不同类型)的参数。它应该检查第一个值是否在其余值中。我想这样写:

#include <unordered_set>

template <typename T>
static bool value_in(T val, T vals...) {
    // compiles, but uses only vals[0]:
    const std::unordered_set<T> allowed {vals};
    // error: pack expansion does not contain any unexpanded parameter packs:
    // const std::unordered_set<T> allowed {vals...};
    return allowed.find(val) != allowed.end();
}
// usage
enum class Enumeration {one, two, three};
int main () {
    // should return true -> 0
    return value_in(Enumeration::two,
                    Enumeration::one,
                    Enumeration::two) ? 0 : 1;
}

我预计第二个可以工作,但它没有编译,因为

test.cpp: In function ‘bool value_in(T, T, ...)’:
test.cpp:7:46: error: expansion pattern ‘vals’ contains no argument packs

我看到的是“(T, T, ...)”而不是“(T, T...)”,所以我可能搞砸了函数声明并以 C 风格的可变参数函数结束。

如何编写将接受任意数量的相同类型参数的声明?

【问题讨论】:

  • 必须是 C++11 还是你也接受 C++14/C++17 的答案?
  • @max66 我需要它在 c++14 中工作,但即使使用 c++20,我也会支持任何答案 :-) 我只是认为 c++11 中存在所有必要的功能
  • This 是关于该主题的有用论文。

标签: c++ templates c++14 variadic-templates variadic-functions


【解决方案1】:

首先,定义一个C风格的可变参数函数

static bool value_in (T val, T vals, ...)

... 之前的逗号是可选的。

所以你的

static bool value_in(T val, T vals...)

定义两个非可变参数(valvals)和一个未命名的可变参数序列。

如何编写可以接受任意数量的相同类型参数的声明?

有很多方法,但恕我直言,有缺点

一种可能的方式是使用 SFINAE:您可以强制可变参数类型等于第一种类型。

以下是使用模板折叠的 C++17 可能解决方案

template <typename T, typename ... Ts>
std::enable_if_t<(std::is_same<T, Ts>::value && ...), bool>
   value_in (T val, Ts ... vals) 
 {
   const std::unordered_set<T> allowed {val, vals ... };

   return allowed.find(val) != allowed.end();
 }

您也可以在 C++11/C++14 中开发此解决方案,但会稍微复杂一些。

缺点:Ts... 类型是推导出来的,它们必须与T 类型完全相同。

因此,例如,如果您想要一个接受std::string() 列表的函数,则不能使用char const * 调用它

value_in(std::string{"abc"}, "123");

因为Tstd::stringTs...char const *不同,而SFINAE并没有启用value_in

您可以使用std::is_convertible 代替std::is_same,但我建议使用另一种方法,分两步。

首先,您需要一个自定义类型特征(使用 using 助手)从列表中选择第一个类型

template <typename T, typename ...>
struct firstType
 { using type = T; };

template <typename T, typename ... Ts>
using firstType_t = typename firstType<T, Ts...>::type;

现在您可以编写第一步value_in() 来拦截所有值,检测所有类型(无限制)并将它们传递给第二步函数,如下所示

template <typename T, typename ... Ts>
bool value_in (T val, Ts ... vals) 
 { return value_in_helper<T, Ts...>(val, vals...); }

第二步函数使用firstType更改T中的所有Ts...类型

template <typename T, typename ... Ts>
bool value_in_helper (T val, firstType_t<T, Ts> ... vals) 
 {
   const std::unordered_set<T> allowed {val, vals ... };

   return allowed.find(val) != allowed.end();
 }

此解决方案与 C++11 兼容。

缺点:您需要第二步。

优势(恕我直言):此解决方案通过第二步函数,该函数声明接收T 类型,因此也接受可转换为T 的参数。

即:这个解决方案也接受

value_in(std::string{"abc"}, "123");

因为不再需要"123" 恰好是std::string;也可以转换成std::string

【讨论】:

    【解决方案2】:

    我在这里看到两个选项。您可以传递一个std::initializer_list,这会导致函数签名更改为

    #include <initializer_list>
    
    template <typename T>
    static bool value_in(T&& val, std::initializer_list<T> vals)
    {
        /* Implementation as before */
    }
    

    和调用sn-p到

    return value_in(Enumeration::two,
          { Enumeration::one, Enumeration::two }) ? 0 : 1;
    

    注意这里的附加大括号,它们是构造要传递的初始化列表所必需的。这种方法的一个小细节是函数签名,它立即表明只有一种类型可以推导。

    如果感觉输入大括号有问题,请坚持您最初的尝试并调整您的函数,以便

    template <typename S, typename... T>
    static bool value_in(S&& val, T&&... vals) {
       const std::unordered_set<S> allowed {std::forward<T>(vals)...};
    
       /* As before... */
    }
    

    这允许像在原始 sn-p 中一样调用函数。与上面的解决方案相比,这个签名显然有两个模板参数,如果ST 不同,则可能需要再次查看它会失败。

    【讨论】:

    • 我指望编译器来保护我免于传递 S!=T,而 initializer_list 似乎实现了它。我会等待一段时间等待其他没有大括号或基于引用的“不,不可能”的答案,然后接受。
    • @MateuszL 请注意,第二种解决方案不需要大括号,但仍会阻止您传递不兼容的类型。
    猜你喜欢
    • 2021-12-28
    • 1970-01-01
    • 2017-01-04
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多