template example create c++ templates lambda c++11 specialization

example - template<> c++



Especializando una plantilla en una lambda en C++ 0x (3)

Escribí una clase de rasgos que me permite extraer información sobre los argumentos y el tipo de una función u objeto de función en C ++ 0x (probado con gcc 4.5.0). El caso general maneja objetos de función:

template <typename F> struct function_traits { template <typename R, typename... A> struct _internal { }; template <typename R, typename... A> struct _internal<R (F::*)(A...)> { // ... }; typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>; };

Luego tengo una especialización para funciones simples en el alcance global:

template <typename R, typename... A> struct function_traits<R (*)(A...)> { // ... };

Esto funciona bien, puedo pasar una función a la plantilla o un objeto de función y funciona correctamente:

template <typename F> void foo(F f) { typename function_traits<F>::whatever ...; } int f(int x) { ... } foo(f);

¿Qué pasa si, en lugar de pasar una función o objeto de función a foo , quiero pasar una expresión lambda?

foo([](int x) { ... });

El problema aquí es que no se aplica ninguna especialización de function_traits<> . El borrador de C ++ 0x dice que el tipo de expresión es un "tipo de clase única, sin nombre, sin unión". Desenmascarar el resultado de llamar a typeid(...).name() en la expresión me da lo que parece ser la convención de nomenclatura interna de gcc para lambda, main::{lambda(int)#1} , no es algo que sintácticamente represente un Nombre de tipo C ++

En resumen, ¿hay algo que pueda poner en la plantilla aquí?

template <typename R, typename... A> struct function_traits<????> { ... }

que permitirá a esta clase de rasgos aceptar una expresión lambda?


Al delegar parte del trabajo en una serie de plantillas de funciones en lugar de una plantilla de clase , puede extraer la información relevante.

Primero, debo decir que el método relevante es un método const , para una lambda (para una lambda no capturable, no genérica, no mutable ). Entonces no podrás distinguir entre una lambda verdadera y esto:

struct { int operator() (int) const { return 7; } } object_of_unnamed_name_and_with_suitable_method;

Por lo tanto, debo suponer que no desea un "tratamiento especial" para lambdas, y no desea probar si un tipo es de tipo lambda, y que en su lugar simplemente desea extraer el tipo de devolución y el tipo de todos los argumentos, para cualquier objeto que sea lo suficientemente simple. Por "lo suficientemente simple" quiero decir, por ejemplo, que el método operator() no es en sí mismo una plantilla. Y, para obtener información adicional, un booleano que nos diga si un método de operator() estaba presente y usado, en contraposición a una función simple anterior.

// First, a convenient struct in which to store all the results: template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args> struct function_traits_results { constexpr static bool is_method = is_method_; constexpr static bool is_const_method = is_const_method_; typedef C class_type; // void for plain functions. Otherwise, // the functor/lambda type typedef R return_type; typedef tuple<Args...> args_type_as_tuple; }; // This will extract all the details from a method-signature: template<typename> struct intermediate_step; template<typename R, typename C, typename ...Args> struct intermediate_step<R (C::*) (Args...)> // non-const methods : public function_traits_results<true, false, C, R, Args...> { }; template<typename R, typename C, typename ...Args> struct intermediate_step<R (C::*) (Args...) const> // const methods : public function_traits_results<true, true, C, R, Args...> { }; // These next two overloads do the initial task of separating // plain function pointers for functors with ::operator() template<typename R, typename ...Args> function_traits_results<false, false, void, R, Args...> function_traits_helper(R (*) (Args...) ); template<typename F, typename ..., typename MemberType = decltype(&F::operator()) > intermediate_step<MemberType> function_traits_helper(F); // Finally, the actual `function_traits` struct, that delegates // everything to the helper template <typename T> struct function_traits : public decltype(function_traits_helper( declval<T>() ) ) { };


Creo que es posible especializar los rasgos de lambdas y hacer coincidencia de patrones en la firma del functor sin nombre. Aquí está el código que funciona en g ++ 4.5. Aunque funciona, la coincidencia de patrones en lambda parece estar funcionando en contra de la intuición. Tengo comentarios en línea.

struct X { float operator () (float i) { return i*2; } // If the following is enabled, program fails to compile // mostly because of ambiguity reasons. //double operator () (float i, double d) { return d*f; } }; template <typename T> struct function_traits // matches when T=X or T=lambda // As expected, lambda creates a "unique, unnamed, non-union class type" // so it matches here { // Here is what you are looking for. The type of the member operator() // of the lambda is taken and mapped again on function_traits. typedef typename function_traits<decltype(&T::operator())>::return_type return_type; }; // matches for X::operator() but not of lambda::operator() template <typename R, typename C, typename... A> struct function_traits<R (C::*)(A...)> { typedef R return_type; }; // I initially thought the above defined member function specialization of // the trait will match lambdas::operator() because a lambda is a functor. // It does not, however. Instead, it matches the one below. // I wonder why? implementation defined? template <typename R, typename... A> struct function_traits<R (*)(A...)> // matches for lambda::operator() { typedef R return_type; }; template <typename F> typename function_traits<F>::return_type foo(F f) { return f(10); } template <typename F> typename function_traits<F>::return_type bar(F f) { return f(5.0f, 100, 0.34); } int f(int x) { return x + x; } int main(void) { foo(f); foo(X()); bar([](float f, int l, double d){ return f+l+d; }); }


El truco void_t puede ayudar. ¿Cómo funciona `void_t` ?

A menos que tenga C ++ 17, deberá incluir la definición de void_t :

template<typename... Ts> struct make_void { typedef void type;}; template<typename... Ts> using void_t = typename make_void<Ts...>::type;

Agregue un argumento de plantilla adicional a la plantilla original, predeterminada para void :

template <typename T, typename = void> struct function_traits;

El objeto de rasgos para funciones simples es el mismo que ya tienes:

template <typename R, typename... A> struct function_traits<R (*)(A...)> { using return_type = R; using class_type = void; using args_type = std:: tuple< A... >; };

Para métodos no const:

template <typename R, typename... A> struct function_traits<R (*)(A...)> { using return_type = R; using class_type = void; using args_type = std:: tuple< A... >; };

No te olvides de los métodos const :

template <typename R, typename C, typename... A> struct function_traits<R (C::*)(A...) const> // const { using return_type = R; using class_type = C; using args_type = std:: tuple< A... >; };

Finalmente, el rasgo importante. Dado un tipo de clase, incluidos los tipos lambda, queremos reenviar de T a decltype(&T::operator()) . Queremos asegurarnos de que este rasgo solo esté disponible para los tipos T para los cuales ::operator() esté disponible, y esto es lo que void_t hace por nosotros. Para aplicar esta restricción, necesitamos poner &T::operator() en la firma de rasgo en algún lugar, por lo tanto, la template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T> struct function_traits<T, void_t< decltype(&T::operator()) > > : public function_traits< decltype(&T::operator()) > { };

El método operator () en lambdas (no mutable , no genérico) es const , lo que explica por qué necesitamos la plantilla const anterior.

Pero finalmente esto es muy restrictivo. Esto no funcionará con lambdas genéricos u objetos con el operator() plantilla operator() . Si reconsidera su diseño, encontrará un enfoque diferente que sea más flexible.