2010-09-06 10 views
6

Digamos que tienes alguna clase de objetivo con alguno de sus métodos:valor de retorno vacío de una función que se utiliza como entrada para una función con plantilla es visto como un parámetro

class Subject 
{ 
public: 
    void voidReturn() { std::cout<<__FUNCTION__<<std::endl; } 
    int intReturn() { std::cout<<__FUNCTION__<<std::endl; return 137; } 
}; 

y una clase de valor (similar en concepto a Boost .Any):

struct Value 
{ 
    Value() {} 
    Value(Value const & orig) {} 
    template< typename T > Value(T const & val) {} 
}; 

Y quiero producir un objeto de valor usando un método de la clase Asunto:

Subject subject; 
Value intval(subject.intReturn()); 
Value voidVal(subject.voidReturn()); // compilation error 

obtengo los siguientes errores en VC++ 2008:

error C2664: 'Value::Value(const Value &)' : cannot convert parameter 1 from 'void' to 'const Value &' 
Expressions of type void cannot be converted to other types 

y gcc 4.4.3:

/c/sandbox/dev/play/voidreturn/vr.cpp:67: error: invalid use of void expression 

El contexto de esto es cuando usted quiere usarlo dentro de una clase de plantilla:

template< typename Host, typename Signature > class Method; 

// Specialization for signatures with no parameters 
template< typename Host, typename Return > 
class Method< Host, Return() > 
{ 
public: 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return Value((m_Host->*m_Method)()); } 
private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 

el uso de esta clase método del método que devuelve algo (es decir, intReturn) se vería así:

Method< Subject, int() > intMeth(&subject, &Subject::intReturn); 
Value intValue = intMeth(); 

Sin embargo, hacer esto con el método voidReturn:

Method< Subject, void() > voidMeth(&subject, &Subject::voidReturn); 
Value voidValue = voidMeth(); 

produce errores similares como anteriormente.

Una solución es especializarse aún parcialmente Método para este tipo de retorno void:

template< typename Host > 
class Method< Host, void() > 
{ 
public: 
    typedef void Return; 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return (m_Host->*m_Method)(), Value(); } 
private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 

Además de que sólo sentirse fea, yo también estoy queriendo especializar la clase Método para los números X de los parámetros de la firma, que ya implica una gran cantidad de duplicación de código (con suerte Boost.Preprocessor puede ayudar aquí), y luego agregar una especialización para los tipos de devolución vacía simplemente duplica ese esfuerzo de duplicación.

¿Hay alguna forma de evitar esta segunda especialización para los tipos de devolución de vacío?

+1

+1. Esta es una pregunta muy bien escrita, creo. –

+0

Absolutamente. En segundo lugar que – Chubsdad

Respuesta

4

Puede usar Return y solo especializar operator() manejo. No es necesario duplicar la plantilla completa.

// I think it's a shame if c++0x really gets rid of std::identity. It's soo useful! 
template<typename> struct t2t { }; 

// Specialization for signatures with no parameters 
template< typename Host, typename Return > 
class Method< Host, Return() > 
{ 
public: 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return call(t2t<Return>()); } 

private: 
    Value call(t2t<void>) { return Value(); } 

    template<typename T> 
    Value call(t2t<T>) { return Value((m_Host->*m_Method)()); } 

private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 
+0

... y la razón para deshacerse de 'identity' es que definir su' operator() 'para usar referencias de rvalue en lugar de' const & '¡rompería demasiado el código instalado! – Potatoswatter

+0

@Potatoswatter, lástima :( –

+0

Sí, eso es lo que estaba buscando. Recuerdo haber leído acerca de Type2Type hace un momento por Alexandrescu, pero desde entonces me olvidé de él. – Chris

2

No, no hay absolutamente ninguna manera de pasar un void. Es una irregularidad en el lenguaje.

La lista de argumentos de la función (void) se traduce como (). Bjarne prefiere lo último a lo primero, y a regañadientes permitió la convención C como un tipo muy limitado de azúcar sintáctico. Ni siquiera puede sustituir un alias typedef de void, y ciertamente no puede tener ningún otro argumento.

Personalmente creo que esta es una mala idea. Si puede escribir void(expr), entonces debe ser capaz de "inicializar" un argumento anónimo de tipo void. Si también pudiera escribir una función con un número arbitrario de argumentos void, habría una forma de ejecutar un número de expresiones en un orden no especificado, que expresaría la concurrencia de una manera.

En cuanto a la manipulación de listas de argumentos de diferentes tamaños (también conocida como variadic), consulte las plantillas variadic en C++ 0x antes de comenzar a tratar de aprender Boost Preprocessor.

+0

Gracias, tenía miedo de eso. Pensé que había visto algo en la función boost donde estaban haciendo algo similar pero aparentemente no. Y gracias por la sugerencia de plantillas variadic, desafortunadamente no actualicé mis compiladores (todavía). – Chris

+0

"Una lista de argumentos de función con un argumento de tipo void se ajusta para que sea una lista sin argumentos." -> en realidad es aún más estricto. Es de esta manera en C99, pero en C++ el reemplazo es puramente sintáctico: la secuencia de tokens "(vacío)" denota una lista de cero parámetros. Pero, por ejemplo, "(Void)" donde "Void" indica que el tipo de vacío es ilegal en C++. –

+0

@Johannes: arreglado. – Potatoswatter

Cuestiones relacionadas