2010-12-26 8 views
6

He leído las diversas autoridades al respecto, incluyen Dewhurst y aún no han logrado llegar a ninguna parte con esta pregunta aparentemente simple.SFINAE y detectar si un objeto de función C++ devuelve vacío

Lo que quiero hacer es llamada un C++ function object, (básicamente, cualquier cosa puede llamar, una función pura o una clase con()), y devolver su valor, si eso no es nula, o " verdadero "de lo contrario.

using std: 

struct Foo { 
    void operator()() { cout << "Foo/"l; } 
}; 
struct Bar { 
    bool operator()() { cout << "Bar/"; return true; } 
}; 

Foo foo; 
Bar bar; 
bool baz() { cout << "baz/"; return true; } 
void bang() { cout << "bang/"; } 

const char* print(bool b) { cout << b ? "true/" : "false/"; } 

template <typename Functor> bool magicCallFunction(Functor f) { 
    return true; // Lots of template magic occurs here 
       // that results in the functor being called. 
} 

int main(int argc, char** argv) { 
    print(magicCallFunction(foo)); 
    print(magicCallFunction(bar)); 
    print(magicCallFunction(baz)); 
    print(magicCallFunction(bang)); 
    printf("\n"); 
} 
// Results: Foo/true/Bar/true/baz/true/bang/true 

ACTUALIZACIÓN

Gracias por los pensamientos e ideas!

Con base en esto, yo realmente decidido llevar toda mi nivel de plantillas uno - así que en vez tengo:

bool eval(bool (*f)()) { return (*f)(); } 

bool eval(void (*f)()) { (*f)(); return true; } 

template <typename Type> 
bool eval(Type* obj, bool (Type::*method)()) { return (obj->*method)(); } 

template <typename Type> 
bool eval(Type* obj, void (Type::*method)()) { (obj->*method)(); return true; } 

y genéricos clases para llevar a los diversos objetos y métodos alrededor. ¡Gracias a Mr.Ree por el código que me empujó en esa dirección!

Respuesta

1

¿No sería más fácil implementar una versión no operativa sobrecargada de print (void)?

Ahh bien. Las plantillas de funciones y la sobrecarga manejarán esto fácilmente en el tiempo de ejecución.

Se pone algo más pegajoso si hubiera querido manejar esto en tiempo de compilación, para usar con macros #if o compilaciones de tiempo de compilación estática.

Pero ya que sólo desea que el primero, puedo sugerir algo así como un punto de partida:.

(Probado bajo (GCC) 3.4.4 4.0.1 & - Lo sé, lo que tenga que actualizar !)

#include <iostream> 
using namespace std; 

struct Foo { 
    void operator()() {} 
}; 
struct Bar { 
    bool operator()() { return false; } 
}; 
Foo foo; 
Bar bar; 
bool baz() { return false; } 
void bang() {} 


struct IsVoid 
{ 
    typedef char YES[1]; 
    typedef char NO[2]; 

     /* Testing functions for void return value. */ 

    template <typename T> 
    static IsVoid::NO & testFunction(T (*f)()); 

    static IsVoid::YES & testFunction(void (*f)()); 

    static IsVoid::NO & testFunction(...); 

     /* Testing Objects for "void operator()()" void return value. */ 

    template <typename C, void (C::*)()> 
    struct hasOperatorMethodStruct { }; 

    template <typename C> 
    static YES & testMethod(hasOperatorMethodStruct<C, &C::operator()> *); 

    template <typename C> 
    static NO & testMethod(...); 


     /* Function object method to call to perform test. */ 
    template <typename T> 
    bool operator() (T & t) 
    { 
    return ( (sizeof(IsVoid::testFunction(t)) == sizeof(IsVoid::YES)) 
      || (sizeof(IsVoid::testMethod<T>(0)) == sizeof(IsVoid::YES))); 
    } 
}; 


#define BOUT(X) cout << # X " = " << boolToString(X) << endl; 

const char * boolToString(int theBool) 
{ 
    switch (theBool) 
    { 
    case true: return "true"; 
    case false: return "false"; 
    default:  return "unknownvalue"; 
    } 
} 

int main() 
{ 
    IsVoid i; 

    BOUT(IsVoid()(foo)); 
    BOUT(IsVoid()(bar)); 
    BOUT(IsVoid()(baz)); 
    BOUT(IsVoid()(bang)); 
    cout << endl; 

    BOUT(i(foo)); 
    BOUT(i(bar)); 
    BOUT(i(baz)); 
    BOUT(i(bang)); 
} 



bien, comienzo a ver más del problema.

Mientras que podemos hacer algo en la línea de lo siguiente:

#include <iostream> 
using namespace std; 

struct FooA { 
    void operator()() {} 
}; 
struct FooB { 
    bool operator()() { return false; } 
}; 
struct FooC { 
    int operator()() { return 17; } 
}; 
struct FooD { 
    double operator()() { return 3.14159; } 
}; 
FooA fooA; 
FooB fooB; 
FooC fooC; 
FooD fooD; 

void barA() {} 
bool barB() { return false; } 
int barC() { return 17; } 
double barD() { return 3.14159; } 


namespace N 
{ 
     /* Functions */ 

    template <typename R> 
    R run(R (*f)()) { return (*f)(); } 

    bool run(void (*f)()) { (*f)(); return true; } 


     /* Methods */ 

    template <typename T, typename R> 
    R run(T & t, R (T::*f)()) { return (t .* f)(); } 

    template <typename T> 
    bool run(T & t, void (T::*f)()) { (t .* f)(); return true; } 
}; 


#define SHOW(X) cout << # X " = " << (X) << endl; 
#define BOUT(X) cout << # X " = " << boolToString(X) << endl; 

const char * boolToString(int theBool) 
{ 
    switch (theBool) 
    { 
    case true: return "true"; 
    case false: return "false"; 
    default:  return "unknownvalue"; 
    } 
} 


int main() 
{ 
    SHOW(N::run(barA)); 
    BOUT(N::run(barA)); 
    SHOW(N::run(barB)); 
    BOUT(N::run(barB)); 
    SHOW(N::run(barC)); 
    SHOW(N::run(barD)); 
    cout << endl; 

    SHOW(N::run(fooA,&FooA::operator())); 
    BOUT(N::run(fooA,&FooA::operator())); 
    SHOW(N::run(fooB,&FooB::operator())); 
    BOUT(N::run(fooB,&FooB::operator())); 
    SHOW(N::run(fooC,&FooC::operator())); 
    SHOW(N::run(fooD,&FooD::operator())); 
} 

Usted todavía tiene que hacer desagradable necesidad de alimentar & CLASE :: operador() como argumento.


En última instancia, si bien podemos determinar si el operador del objeto () método devuelve un vacío, no podemos normalmente sobrecarga basado en tipos de retorno.

Podemos superar esa limitación de sobrecarga mediante la especialización de plantilla. Pero luego nos metemos en esta fealdad en la que todavía necesitamos especificar tipos ... ¡Especialmente el tipo de devolución! Ya sea manualmente o pasando un argumento adecuado desde el cual podemos extraer los tipos necesarios.

BTW: #define macros no ayudará tampoco. Herramientas como?: Requieren el mismo tipo para ambos? y: parte

Así que esto es lo mejor que puedo hacer ...



Por supuesto ...

Si usted no necesita el tipo de retorno ...

Si solo está pasando el resultado a otra función ...

Puede hacer algo como esto:

#include <iostream> 
using namespace std; 

struct FooA { 
    void operator()() {} 
}; 
struct FooB { 
    bool operator()() { return false; } 
}; 
struct FooC { 
    int operator()() { return 17; } 
}; 
struct FooD { 
    double operator()() { return 3.14159; } 
}; 
FooA fooA; 
FooB fooB; 
FooC fooC; 
FooD fooD; 

void barA() {} 
bool barB() { return false; } 
int barC() { return 17; } 
double barD() { return 3.14159; } 


#define SHOW(X) cout << # X " = " << (X) << endl; 

namespace N 
{ 
    template <typename T, typename R> 
    R run(T & t, R (T::*f)()) { return (t .* f)(); } 

    template <typename T> 
    bool run(T & t, void (T::*f)()) { (t .* f)(); return true; } 


    template <typename T> 
    void R(T & t) 
    { 
    SHOW(N::run(t, &T::operator())); 
    } 

    template <typename T> 
    void R(T (*f)()) 
    { 
    SHOW((*f)()); 
    } 

    void R(void (*f)()) 
    { 
    (*f)(); 
    SHOW(true); 
    } 
}; 


int main() 
{ 
    N::R(barA); 
    N::R(barB); 
    N::R(barC); 
    N::R(barD); 
    N::R(fooA); 
    N::R(fooB); 
    N::R(fooC); 
    N::R(fooD); 
} 
+0

Probado y funciona en gcc 4.2.1. –

+0

La "impresión" es solo para probar lo que está pasando ... lo que estoy haciendo es envolver métodos y funciones para pasar al mecanismo de devolución de llamada asincrónico de otra persona. La mayoría de mis métodos y funciones vuelven vacíos, a veces necesito poder devolver un valor booleano a la devolución de llamada asíncrona de un tercero. –

+0

¡Esto es muy resbaladizo! No hay uno o dos sino tres trucos que no entendí bien, uno de los cuales son los dos casos NO en la sobrecarga de testFunction, uno es el violín hasOperatorMethodStruct, y el otro es la manera de combinar estas dos condiciones. Muy educativo, te debo una bebida refrescante de tu elección. –

1

Quizás pueda utilizar el hecho de que void & no tiene sentido como type pero void * lo hace.

-1

tratan de especializarse para el tipo de retorno vacío:

template<class F> 
class traits; 

template<class F, class T> 
class traits<T (F)()>; 

template<class F> 
class traits<void (F)()>; 

creo ...

0

Con C++ 0x usted podría hacerlo fácilmente mediante el uso de decltype.

+0

¿cómo funcionaría el decltype? – paulm

0

Si puede usar Boost, probablemente le sirva el siguiente código. Supongo que todas las funciones/funtores son nulary como en su pregunta. Sin embargo, para usar esto, result_type tiene que definirse en todos los funtores (clase de función).

#include <boost/utility/result_of.hpp> 
#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits.hpp> 
using namespace boost; // Sorry, for brevity 

template< class F > 
// typename result_of< F() >::type 
typename disable_if< 
    is_void< typename result_of< F() >::type > 
, typename result_of< F() >::type 
>::type 
f(F const& x) 
{ 
    return x(); 
} 

template< class F > 
typename enable_if< 
    is_void< typename result_of< F() >::type >, bool 
>::type 
f(F const& x) 
{ 
    x(); 
    return true; 
} 

template< class T > 
T f(T x()) 
{ 
    return x(); 
} 

bool f(void x()) 
{ 
    x(); 
    return true; 
} 

static void void_f() {} 
static int int_f() { return 1; } 

struct V { 
    typedef void result_type; 
    result_type operator()() const {} 
}; 

struct A { 
    typedef int result_type; 
    result_type operator()() const { return 1; } 
}; 

int main() 
{ 
    A a; 
    V v; 
    f(void_f); 
    f(int_f); 
    f(a); 
    f(v); 
} 

Esperanza esto ayuda

6

Para detectar un valor de retorno vacío en tiempo de compilación, el truco estándar es sobrecargar operator,. Lo bueno del operador de coma es que puede tomar un parámetro de vacío, y en este caso se predetermina al construido en operator,. En código:

template <typename> tag {}; 

template <typename T> 
tag<T> operator,(T, tag<void>); 

Ahora, expr, tag<void>() tiene tipo tag<typeof(expr)> incluso si tiene expr tipo void. A continuación, puede ver esto con los trucos habituales:

char (&test(tag<void>))[1]; 
template <typename T> char (&test(tag<T>))[2]; 

template <typename F> 
struct nullary_functor_traits 
{ 
    static const bool returns_void = sizeof(test((factory()(), tag<void>()))) == 1; 
private: 
    static F factory();  
}; 
Cuestiones relacionadas