tag significado nombres hacer graffitis graffiti estilos definicion creador como bombing abecedario c++ c++11 templates template-meta-programming callable-object

c++ - significado - tags graffiti estilos



Cómo verificar si el argumento de la plantilla es llamable con una firma dada (6)

En C ++ 17 hay un rasgo is_invocable<Callable, Args...> , que hace exactamente lo que pides. Su ventaja sobre is_convertible<std::function<Signature>,...> es que no tiene que especificar el tipo de retorno. Puede sonar como una exageración, pero recientemente tuve un problema que tuve que usar, exactamente mi función de envoltura dedujo su tipo de retorno de Callable pasado, pero he pasado lambda con plantilla como esta [](auto& x){return 2*x;} , por lo que el tipo de retorno se dedujo en subcall. No pude convertirlo en std::function y terminé usando la implementación local de is_invocable para C ++ 14. No puedo encontrar el enlace desde donde lo obtuve ... De todos modos, el código:

template <class F, class... Args> struct is_invocable { template <class U> static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type()); template <class U> static auto test(...) -> decltype(std::false_type()); static constexpr bool value = decltype(test<F>(0))::value; };

y para tu ejemplo:

struct A { using Signature = void(int, double); template <typename Callable> void Register(Callable &&callable) { static_assert(is_invocable<Callable,int,double>::value, "not foo(int,double)"); callback = callable; } std::function<Signature> callback; };

Básicamente, lo que quiero lograr es la verificación en tiempo de compilación (con un mensaje de error posiblemente agradable) que registró como ejecutable (ya sea una función, un lambda, una estructura con operador de llamada) tiene la firma correcta. Ejemplo (los contenidos de static_assert deben ser rellenados):

struct A { using Signature = void(int, double); template <typename Callable> void Register(Callable &&callable) { static_assert(/* ... */); callback = callable; } std::function<Signature> callback; };


En ese caso, puede utilizar una biblioteca muy simple Boost.Callable Traits .

Ejemplo de uso:

#include <boost/callable_traits.hpp> #include <iostream> #include <tuple> template<typename F> void register_handler(F&) { if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int&, double)>) { std::cout << "Register handler with signature void(int&, double)" << std::endl; } else if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int)>) { std::cout << "Register handler with signature void(int)" << std::endl; } } void func(int&, double) {} auto lambda = [](int) {}; int main() { { register_handler(func); register_handler(lambda); } { using function_type = boost::callable_traits::function_type_t<decltype(func)>; using expected_function_type = void(int&, double); std::cout << std::boolalpha << std::is_same_v<expected_function_type, function_type> << std::endl; } }

Para obtener el tipo de función, puede usar boost::callable_traits::function_type_t<decltype(func)> .

Como puede ver en register_handler funciones main y register_handler , es posible comparar el tipo de tipo de función expected_function_type con el tipo de función ( boost::callable_traits::function_type_t<FUNCTION> ) usando std::is_same_v "function" -> https://en.cppreference.com/w/cpp/types/is_same

Si quieres ejecutar mi ejemplo, compílalo con boost 1.66.0 y c ++ 17 usando, por ejemplo, gcc 7.1.0. Here puedes hacerlo online :)


La mayoría de las respuestas se centran básicamente en responder a la pregunta: ¿puede llamar al objeto de función dado con valores de estos tipos? Esto no es lo mismo que hacer coincidir la firma, ya que permite muchas conversiones implícitas que usted dice que no quiere. Para obtener una coincidencia más estricta, tenemos que hacer un montón de TMP. En primer lugar, esta respuesta: la función de llamada con parte de los argumentos variadic muestra cómo obtener los tipos exactos de los argumentos y el tipo de devolución de una llamada. Código reproducido aquí:

template <typename T> struct function_traits : public function_traits<decltype(&T::operator())> {}; template <typename ClassType, typename ReturnType, typename... Args> struct function_traits<ReturnType(ClassType::*)(Args...) const> { using result_type = ReturnType; using arg_tuple = std::tuple<Args...>; static constexpr auto arity = sizeof...(Args); }; template <typename R, typename ... Args> struct function_traits<R(&)(Args...)> { using result_type = R; using arg_tuple = std::tuple<Args...>; static constexpr auto arity = sizeof...(Args); };

Una vez hecho esto, ahora puede poner una serie de aserciones estáticas en su código:

struct A { using Signature = void(int, double); template <typename Callable> void Register(Callable &&callable) { using ft = function_traits<Callable>; static_assert(std::is_same<int, std::decay_t<std::tuple_element_t<0, typename ft::arg_tuple>>>::value, ""); static_assert(std::is_same<double, std::decay_t<std::tuple_element_t<1, typename ft::arg_tuple>>>::value, ""); static_assert(std::is_same<void, std::decay_t<typename ft::result_type>>::value, ""); callback = callable; } std::function<Signature> callback; };

Ya que estás pasando por valor, esto es básicamente todo lo que necesitas. Si está pasando por referencia, agregaría una aserción estática adicional donde se usa una de las otras respuestas; Probablemente la respuesta de songyuanyao. Esto se ocuparía de los casos en los que, por ejemplo, el tipo de base era el mismo, pero la calificación constante iba en la dirección incorrecta.

Por supuesto, podría hacer que todo esto sea genérico sobre el tipo Signature , en lugar de hacer lo que hago (simplemente repitiendo los tipos en la aserción estática). Esto sería mejor, pero habría agregado TMP aún más complejo a una respuesta ya no trivial; Si sientes que usarás esto con muchas Signature diferentes o que está cambiando a menudo, probablemente también valga la pena agregar ese código.

Aquí hay un ejemplo en vivo: http://coliru.stacked-crooked.com/a/cee084dce9e8dc09 . En particular, mi ejemplo:

void foo(int, double) {} void foo2(double, double) {} int main() { A a; // compiles a.Register([] (int, double) {}); // doesn''t //a.Register([] (int, double) { return true; }); // works a.Register(foo); // doesn''t //a.Register(foo2); }


Puede usar std::is_convertible (desde C ++ 11), por ejemplo

static_assert(std::is_convertible_v<Callable&&, std::function<Signature>>, "Wrong Signature!");

o

static_assert(std::is_convertible_v<decltype(callable), decltype(callback)>, "Wrong Signature!");

LIVE


Puede utilizar el lenguaje de detección, que es una forma de sfinae. Creo que esto funciona en c ++ 11.

template <typename...> using void_t = void; template <typename Callable, typename enable=void> struct callable_the_way_i_want : std::false_type {}; template <typename Callable> struct callable_the_way_i_want <Callable, void_t <decltype (std::declval <Callable>()(int {},double {}))>> : std::true_type {};

Entonces puedes escribir una aserción estática en tu código así:

static_assert (is_callable_the_way_i_want <Callable>::value, "Not callable with required signature!");

La ventaja de esto sobre las respuestas que veo arriba es:

  • Funciona para cualquier llamada, no solo una lambda
  • No hay tiempo de ejecución o negocio std::function . std::function puede causar una asignación dinámica, por ejemplo, que de otra manera no sería necesaria.
  • puedes escribir un static_assert contra de la prueba y poner un buen mensaje de error legible para el usuario allí

Tartan Llama escribió una excelente publicación de blog sobre esta técnica y varias alternativas, ¡échale un vistazo! https://blog.tartanllama.xyz/detection-idiom/

Si necesita hacer esto mucho, entonces es posible que desee consultar la biblioteca callable_traits.


Si acepta transformar A en una clase de plantilla variadic, puede usar decltype() , para activar el Register solo si el nombre de la aplicación es compatible, de la siguiente manera

template <typename R, typename ... Args> struct A { using Signature = R(Args...); template <typename Callable> auto Register (Callable && callable) -> decltype( callable(std::declval<Args>()...), void() ) { callback = callable; } std::function<Signature> callback; };

De esta manera, si lo prefiere, llamando a Register() con una función incompatible, puede obtener un error de software y activar otra función de Register() .

void Register (...) { /* do something else */ };