c++ templates variadic-templates c++17 c++-faq

c++ - Restringir argumentos de plantillas variadic



c++ variadic templates (5)

¿Podemos restringir los argumentos de plantilla variadic a cierto tipo? Es decir, lograr algo como esto (C ++ no real, por supuesto):

struct X {}; auto foo(X... args)

Aquí mi intención es tener una función que acepte un número variable de parámetros X

Lo más cercano que tenemos es esto:

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

pero esto acepta cualquier tipo de parámetro.


C ++ 14

Desde C ++ 14 puedes usar también plantillas de variables , especialización parcial y static_assert para hacer eso. Como ejemplo:

#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>(); }

También puede usar check junto con std::enable_if_t como tipo de devolución, si no desea usar static_assert por algún motivo desconocido:

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

Y así...

C ++ 11

En C ++ 11, también puede diseñar una solución que detenga la recursión inmediatamente cuando se encuentre un tipo que no se va a aceptar. Como ejemplo:

#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>(); }

Como se mencionó anteriormente, puede usar también check en el tipo de devolución o donde quiera.


¿Qué hay de la siguiente solución?

--- EDITAR --- Mejorado siguiendo la sugerencia de bolov y Jarod42 (¡gracias!)

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

Puede declarar foo() para recibir todos los tipos de argumentos ( Args ... args ) pero (recursivamente) implementarlo solo para un tipo ( int en este ejemplo).


Qué static_assert método static_assert y helper template (solución 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; }

Uno más c ++ 11 este usa solución "one-liner" basada en 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) { }


Sí, es posible. En primer lugar, debe decidir si desea aceptar solo el tipo, o si desea aceptar un tipo implícitamente convertible. Utilizo std::is_convertible en los ejemplos porque imita mejor el comportamiento de los parámetros que no son de plantilla, por ejemplo, un parámetro long long aceptará un argumento int . Si por alguna razón necesita solo ese tipo para ser aceptado, reemplace std::is_convertible con std:is_same (puede que necesite agregar std::remove_reference y std::remove_cv ).

Desafortunadamente, en C++ reducción de la conversión, por ejemplo ( long long to int e incluso duble to int ) son conversiones implícitas. Y si bien en una configuración clásica puede recibir advertencias cuando ocurren, no la obtiene con std::is_convertible . Al menos no en la llamada. Puede obtener las advertencias en el cuerpo de la función si realiza dicha tarea. Pero con un pequeño truco también podemos obtener el error en el sitio de llamadas con plantillas.

Así que sin más preámbulos aquí va:

La plataforma de prueba:

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

Conceptos

Aún no está aquí, pero pronto. Esta será la solución más simple, clara y elegante

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:

Obtenemos un error muy bueno. Especialmente el

''Convertible'' no estaba satisfecho

es dulce:

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

Lidiar con el estrechamiento

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

Hacemos uso de la expresión de doblez muy agradable:

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

Desafortunadamente obtenemos un error menos claro:

deducción / sustitución del argumento de la plantilla fallida: [...]

Estrechamiento

Podemos evitar el estrechamiento, pero tenemos que cocinar un rasgo is_convertible_no_narrowing (tal vez nombrarlo de manera diferente):

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

Creamos un auxiliar de conjunción:
tenga en cuenta que en C++17 habrá una std::conjunction , pero tomará argumentos 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> {};

y ahora podemos tener nuestra función:

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

solo pequeños ajustes a la versión 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


Ya lo tienes desde el estándar C ++ 11.

Un simple std::array (caso especial de std::tuple donde todos los elementos de tupla comparten el mismo tipo) será suficiente.

Sin embargo, si desea utilizarlo en una función de plantilla, puede utilizar mejor una''std :: initializer_list` como en el siguiente ejemplo:

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

Esta es una solución realmente simple que resuelve tu problema. El uso de argumentos de plantilla variadic también es una opción, pero agrega complejidad innecesaria a su código. Recuerde que su código debe ser legible por otros, incluido usted mismo después de un tiempo.