c++ - significado - tags graffiti nombres




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

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

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 :)


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.





callable-object