限制可变参数模板参数

时间:2016-09-23 11:00:11

标签: c++ templates variadic-templates c++17 c++-faq

我们可以将可变参数模板参数限制为某种类型吗?即,实现类似的东西(当然不是真正的C ++):

struct X {};

auto foo(X... args)

这里我的目的是拥有一个接受可变数量的X参数的函数。

我们最接近的是:

template <class... Args>
auto foo(Args... args)

但是它接受任何类型的参数。

5 个答案:

答案 0 :(得分:46)

是的,这是可能的。首先,您需要决定是否只接受类型,或者是否要接受隐式可转换类型。我在示例中使用std::is_convertible,因为它更好地模仿非模板化参数的行为,例如long long参数将接受int参数。如果由于某种原因您需要接受该类型,请将std::is_convertible替换为std:is_same(您可能需要添加std::remove_referencestd::remove_cv)。

不幸的是,C++缩小了转化率,例如(long longint甚至dubleint)都是隐式转换。而在经典设置中,当发生这些警告时,您可以收到警告,而std::is_convertible则无法获得警告。至少不在电话会议上。如果进行此类分配,您可能会在函数体中获得警告。但是通过一个小技巧,我们也可以通过模板在呼叫站点获得错误。

所以不用再多说了:

测试台:

struct X {};
struct Derived : X {};
struct Y { operator X() { return {}; }};
struct Z {};

foo_x : function that accepts X arguments

int main ()
{
   int i{};
   X x{};
   Derived d{};
   Y y{};
   Z z{};

   foo_x(x, x, y, d); // should work
   foo_y(x, x, y, d, z); // should not work due to unrelated z
};

概念

还没到,但很快。这将是最简单,清晰和优雅的解决方案

template <class From, class To>
concept constexpr bool Convertible = std::is_convertible_v<From, To>;

template <Convertible<X>... Args>
auto foo_x(Args... args) {}

foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // error:

我们得到了一个非常好的错误。特别是

  

'可转换'不满意

很甜蜜:

error: cannot call function 'auto foo_x(Args ...) [with Args = {X, X, Y, Derived, Z}]'
     foo_x(x, x, y, d, z);
                        ^
note:   constraints not satisfied
auto foo_x(Args... args)
     ^~~~~
note: in the expansion of 'Convertible<Args, X>...'
note:     'Convertible<Z, X>' was not satisfied

处理缩小范围:

template <class From, class To>
concept constexpr bool Convertible_no_narrow = requires(From f, To t) {
    t = {f};
};

template <Convertible_no_narrow<int>... Args>
auto foo_ni(Args... args) {}

foo_ni(24, 12); // OK
foo_ni(24, 12, 15.2);
// error:
// 'Convertible_no_narrow<double, int>' was not satisfied

C ++ 17

我们使用非常好的fold expression

template <class... Args,
         class Enable = std::enable_if_t<(... && std::is_convertible_v<Args, X>)>>
auto foo_x(Args... args) {}

foo_x(x, x, y, d, z);    // OK
foo_x(x, x, y, d, z, d); // error

不幸的是,我们得到的错误不太明确:

  

模板参数推断/替换失败:[...]

缩小

我们可以避免缩小范围,但我们必须做一个特性is_convertible_no_narrowing(可能用不同的名称命名):

template <class From, class To>
struct is_convertible_no_narrowing_impl {
  template <class F, class T,
            class Enable = decltype(std::declval<T &>() = {std::declval<F>()})>
  static auto test(F f, T t) -> std::true_type;
  static auto test(...) -> std::false_type;

  static constexpr bool value =
      decltype(test(std::declval<From>(), std::declval<To>()))::value;
};

template <class From, class To>
struct is_convertible_no_narrowing
    : std::integral_constant<
          bool, is_convertible_no_narrowing_impl<From, To>::value> {};

C ++ 14

我们创建一个连词助手:
请注意,在C++17中会有std::conjunction,但需要std::integral_constant个参数

template <bool... B>
struct conjunction {};

template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
    : std::integral_constant<bool, Head && conjunction<Tail...>::value>{};

template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};

现在我们可以拥有我们的功能:

template <class... Args,
          class Enable = std::enable_if_t<
              conjunction<std::is_convertible<Args, X>::value...>::value>>
auto foo_x(Args... args) {}


foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error

C ++ 11

对C ++ 14版本进行微调:

template <bool... B>
struct conjunction {};

template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
    : std::integral_constant<bool, Head && conjunction<Tail...>::value>{};

template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};

template <class... Args,
          class Enable = typename std::enable_if<
              conjunction<std::is_convertible<Args, X>::value...>::value>::type>
auto foo_x(Args... args) -> void {}

foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error

答案 1 :(得分:5)

C ++ 14

从C ++ 14开始,你也可以使用变量模板,部分特化和static_assert来做到这一点。举个例子:

#include <type_traits>

template<template<typename...> class, typename...>
constexpr bool check = true;

template<template<typename...> class C, typename U, typename T, typename... O>
constexpr bool check<C, U, T, O...> = C<T, U>::value && check<C, U, O...>;

template<typename... T>
void f() {
    // use std::is_convertible or whichever is the best trait for your check
    static_assert(check<std::is_convertible, int, T...>, "!");
    // ...
}

struct S {};

int main() {
    f<int, unsigned int, int>();
    // this won't work, for S is not convertible to int
    // f<int, S, int>();
}

如果您因某些未知原因不想使用check,也可以将std::enable_if_tstatic_assert结合使用作为返回类型:

template<typename... T>
std::enable_if_t<check<std::is_convertible, int, T...>>
f() {
    // ...
}

等等......

C ++ 11

在C ++ 11中,您还可以设计一个解决方案,在遇到不可接受的类型时立即停止递归。举个例子:

#include <type_traits>

template<bool...> struct check;
template<bool... b> struct check<false, b...>: std::false_type {};
template<bool... b> struct check<true, b...>: check<b...> {};
template<> struct check<>: std::true_type {};

template<typename... T>
void f() {
    // use std::is_convertible or whichever is the best trait for your check
    static_assert(check<std::is_convertible<int, T>::value...>::value, "!");
    // ...
}

struct S {};

int main() {
    f<int, unsigned int, int>();
    // this won't work, for S is not convertible to int
    // f<int, S, int>();
}

如上所述,您也可以在返回类型或任何地方使用check

答案 2 :(得分:4)

以下解决方案如何?

---编辑--- 改进了bolov和Jarod42的建议(谢谢!)

#include <iostream>

template <typename ... Args>
auto foo(Args... args) = delete;

auto foo ()
 { return 0; }

template <typename ... Args>
auto foo (int i, Args ... args)
 { return i + foo(args...); }

int main () 
 {
   std::cout << foo(1, 2, 3, 4) << std::endl;  // compile because all args are int
   //std::cout << foo(1, 2L, 3, 4) << std::endl; // error because 2L is long

   return 0;
 }

您可以声明foo()接收所有类型的参数(Args ... args)但是(递归地)仅为一种类型(在此示例中为int)实现它。

答案 3 :(得分:1)

static_assert和帮助模板方法(c ++ 11解决方案)如何:

template <bool b>
int assert_impl() {
   static_assert(b, "not convertable");
   return 0;
}

template <class... Args>
void foo_x(Args... args) {
    int arr[] {assert_impl<std::is_convertible<Args, X>::value>()...};
    (void)arr;
}

另一个c ++ 11这个使用“单线”sfinae解决方案:

template <class... Args,
          class Enable = decltype(std::array<int, sizeof...(Args)>{typename std::enable_if<std::is_convertible<Args, X>::value, int>::type{}...})>
void foo_x(Args... args) {
}

答案 4 :(得分:1)

自C ++ 11标准以来,您已经拥有它。

一个简单的std::arraystd::tuple的特例,其中所有元组元素共享相同的类型)就足够了。

但是,如果要在模板函数中使用它,最好使用'std :: initializer_list`,如下例所示:

template< typename T >
void foo( std::initializer_list<T> elements );

这是一个非常简单的解决方案,可以解决您的问题。使用可变参数模板参数也是一种选择,但会给代码增加不必要的复杂性。请记住,您的代码应该是其他人可读的,包括您自己一段时间后。