限制可变参数模板参数

bol*_*lov 41 c++ templates c++-faq variadic-templates c++17

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

struct X {};

auto foo(X... args)
Run Code Online (Sandbox Code Playgroud)

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

我们最接近的是:

template <class... Args>
auto foo(Args... args)
Run Code Online (Sandbox Code Playgroud)

但是这接受任何类型的参数.

bol*_*lov 47

对的,这是可能的.首先,您需要决定是否只接受类型,或者是否要接受隐式可转换类型.我std::is_convertible在示例中使用它因为它更好地模仿非模板化参数的行为,例如long long参数将接受int参数.如果由于某种原因您需要接受该类型,请替换std::is_convertiblestd: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
};
Run Code Online (Sandbox Code Playgroud)

概念

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

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:
Run Code Online (Sandbox Code Playgroud)

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

"敞篷车"不满意

很甜蜜:

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
Run Code Online (Sandbox Code Playgroud)

处理缩小范围:

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
Run Code Online (Sandbox Code Playgroud)

C++ 17

我们使用非常好的折叠表达式:

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,我们得到了一个不太清楚

模板参数扣除/替换失败:[...]

变窄

我们可以避免缩小范围,但我们必须做一个特性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> {};
Run Code Online (Sandbox Code Playgroud)

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> {};
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • @DieterLücking它不是博客.是的,它绝对是正确的方式:http://stackoverflow.com/help/self-answer.这是主题.一个明确而具体的编程问题. (7认同)
  • @DieterLücking只要Q/A值得分享就像这里一样,我认为没有理由不去...... (5认同)

sky*_*ack 6

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>();
}
Run Code Online (Sandbox Code Playgroud)

如果您不想出于某些未知原因使用,也可以checkstd::enable_if_t返回类型一起使用static_assert:

template<typename... T>
std::enable_if_t<check<std::is_convertible, int, T...>>
f() {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

等等...

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>();
}
Run Code Online (Sandbox Code Playgroud)

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