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

struct X {};

auto foo(X... args)

在这里,我的目的是要提供一个接受可变数量X参数的函数。

我们最接近的是:
template <class... Args>
auto foo(Args... args)

但这可以接受任何类型的参数。

最佳答案

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

不幸的是,在C++中缩小转换例如(long longint,甚至doubleint)都是隐式转换。在传统的设置中,当发生警告时,您会收到警告,而std::is_convertible不会警告您。至少不是在电话 session 上。如果进行这样的分配,则可能会在函数的主体中得到警告。但是,通过一些技巧,我们也可以使用模板在 call 站点获取错误。

因此,这里不再赘述:

测试台:

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
};

C++ 20概念

还没到这里,但是很快。在gcc主干中可用(2020年3月)。这是最简单,清晰,优雅和安全的解决方案:
#include <concepts>

auto foo(std::convertible_to<X> auto ... args) {}

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

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



很甜:

处理缩小:

我没有在库中找到一个概念,因此我们需要创建一个概念:
template <class From, class To>
concept ConvertibleNoNarrowing = std::convertible_to<From, To>
    && requires(void (*foo)(To), From f) {
        foo({f});
};

auto foo_ni(ConvertibleNoNarrowing<int> auto ... args) {}

foo_ni(24, 12); // OK
foo_ni(24, (short)12); // OK
foo_ni(24, (long)12); // error
foo_ni(24, 12, 15.2); // error

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

09-10 04:54
查看更多