C ++非类型参数包扩展

时间:2018-07-30 12:11:17

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

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

#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风格的可变参数函数结尾。

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

2 个答案:

答案 0 :(得分:4)

首先,定义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(),以拦截所有值,检测al类型(无限制),然后将它们传递给第二步函数,如下所示

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

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

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兼容。

缺点:您需要执行第二步。

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

也就是说,此解决方案也可以接受

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

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

答案 1 :(得分:1)

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

#include <initializer_list>

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

和调用代码段

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... */
}

这允许您像原始代码片段中那样调用函数。与上述解决方案相比,此签名显然具有两个模板参数,如果ST不同,则可能需要重新看一下才能失败。