2010-04-01 16 views
22

He escrito una clase de rasgos que me permite extraer información sobre los argumentos y el tipo de una función o objeto de función en C++ 0x (probado con gcc 4.5.0) . El caso general trata los objetos de función:Especializando una plantilla en una lambda en C++ 0x

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

Entonces tengo una especialización de las funciones de civil en el ámbito global:

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

Esto funciona bien, puedo pasar una función en 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é pasaría si, en lugar de pasar una función o un objeto función en foo, quiero pasar una expresión lambda?

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

El problema aquí es que ni especialización de function_traits<> aplica. 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 al typeid(...).name() en la expresión me da lo que parece ser la convención de nomenclatura interna de gcc para el lambda, main::{lambda(int)#1}, no es algo que sintácticamente represente un nombre de tipo C++.

En pocas palabras, ¿hay algo que pueda poner en la plantilla aquí:

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

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

+0

No. ¿Por qué crees que necesitas algo como esto? – sellibitze

+1

Creo que mi ejemplo dio un caso de uso decente: si tengo un algoritmo genérico que toma una función o objeto de función, puedo usar esta clase de rasgos para determinar no solo el tipo de retorno (que también podría hacerse con decltype hoy en día), sino también los tipos de los argumentos. (Dejé fuera la mayor parte del código para evitar que la publicación sea demasiado larga.) Dado que puedo pasar una función u objeto de función, para propósitos de ortogonalidad también me gustaría poder pasar una lambda. Esto es básicamente un ejercicio académico que surgió de la lectura de "Elementos de Programación". –

+0

@Tony: La respuesta es sí, lo he hecho. Sin embargo, podré volver a esta pregunta un poco más tarde. ¿Qué rasgos estás tratando de obtener? – GManNickG

Respuesta

17

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; }); 
} 
+0

Gracias, eso funcionó. No se me ocurrió simplemente encadenar la plantilla no especializada en una versión especializada como esa. Una vez que lo vi, se ve increíblemente elegante y obvio. –

+0

Me alegra que haya ayudado. Parece que, aunque la sintaxis de llegar al operador() de una lambda es como la función miembro de la clase, lambda es, después de todo, una función independiente anónima y coincide con R (*) (A ...) pero no con R (C :: *)(UN...). – Sumant

+2

Supongo que el razonamiento detrás de esto podría ser que "C" en este caso sería un tipo definido por la implementación, y proporcionar acceso a él a través de un argumento de plantilla nos permitiría hacer cosas sin sentido como definir alias typedef para él. Como no podemos hacer nada con el tipo lambda, excepto llamarlo como una función, tiene más sentido desde el punto de vista del diseño del lenguaje forzar esa especialización que se aplica. –

1

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

En primer lugar, debo decir que el método relevante es un método const, para una lambda (para una lambda que no captura, no genérica, no mutable). Por lo que no será capaz de distinguir la diferencia entre un verdadero lambda y esto:

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

Por lo tanto, debo asumir que usted no quiere un "trato especial" para lambdas, y que no quiero poner a prueba si un tipo es un tipo lambda, y en su lugar usted simplemente quiere 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() es y que no es una plantilla. Y, para obtener información adicional, un booleano que nos diga si un método operator() estuvo presente y se usó, a diferencia de una función antigua normal.



// 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>())) 
{ 
}; 
+0

¿Cómo puedo usar estos rasgos dentro de una plantilla de función, que recibe una lambda como void func (T lambda) {} para declarar una función std :: dentro? – barney

2

El truco void_t puede ayudar. How does `void_t` work?

A menos que tenga 17 C++, tendrá que 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; 

Añadir un argumento de plantilla adicional para la plantilla original, por defecto en void:

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

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

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 se olvide const métodos:

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

Por último, el rasgo importante. Dado un tipo de clase, incluidos los tipos lambda, queremos reenviar desde T a decltype(&T::operator()). Queremos asegurarnos de que esta característica solo esté disponible para los tipos T para los cuales ::operator() está disponible, y esto es lo que void_t hace por nosotros. Para hacer cumplir esta restricción, tenemos que poner en la firma &T::operator() rasgo en algún lugar, por lo tanto, 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 del operador() en lambdas (no mutable, no genérico) es const, lo que explica por qué necesitamos la const plantilla de arriba.

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

+0

Error bajo 'métodos no const'. Parece que copió la función libre uno. Se supone que tiene la firma como la const a continuación – kert

Cuestiones relacionadas