2012-03-22 30 views
7

Formulo esta pregunta después del problema que planteé here.Operador de conversión implementado con static_cast

El punto es bastante simple. Suponga que tiene dos clases de este tipo:

template < class Derived > 
class Base { 
... 
operator const Derived&() const { 
    return static_cast< const Derived& >(*this); 
    } 
... 
}; 

class Specialization : public Base<Specialization> { 
... 
}; 

A continuación, supongamos que tiene una conversión de tipos como éste:

template < class T > 
functionCall(const Base<T>& param) { 
    const T & val(param); 
    ... 
} 

La pregunta es: cuál debe ser el comportamiento que sigan los estándares de esta conversión?

¿Debería ser lo mismo que const T & val(static_cast<const T &> (param)) o debería iterar recursivamente hasta que se desborde la pila? Tenga en cuenta que obtengo el primer comportamiento compilando con GNU g++ y el segundo compilando con Intel icpc.

Ya intenté echar un vistazo al estándar (sección 5.9 en static_cast y sección 12.3 sobre conversiones) pero debido a mi falta de experiencia no pude encontrar la respuesta.

Muchas gracias de antemano a cualquiera que se haya tomado el tiempo de ayudarme con esto.

+2

La sección pertinente sería el de resolución de sobrecarga. –

+0

Si le gustara mi curiosidad, ¿por qué quiere el operador de conversión en 'Base'? –

+1

@ Tony Delroy: 1) para ser menos detallado que llamar explícitamente un method.2 clase) me encontré con este tipo de código de un par de tiempo en la red mientras que intenta conseguir algún conocimiento sobre CRTPs, véase por ejemplo [aquí] (http : //en.wikipedia.org/wiki/Expression_templates) 3) Después de encontrar este comportamiento extraño me puse muy curioso sobre cuál debería ser la correcta :-) – Massimiliano

Respuesta

3

En cuanto a [expr.static.cast] en n3337 (Primer Borrador de Trabajo después de la Norma):

2/ Un valor-I de tipo “CV1 B”, donde es B un tipo de clase, se puede convertir para escribir "referencia a cv2 D", donde D es una clase derivada (Cláusula 10) de B, si existe una conversión estándar válida de "puntero a D" a "puntero a B" [.. .]

4/ De lo contrario, una expresión e se puede convertir explícitamente a un tipo T usando un static_cast de la forma static_cast<T>(e) si la declaración T t(e); está bien formado, por alguna inventaron temporal variable de t [..]

Por lo tanto, me gustaría interpretar el comportamiento de gcc que es la correcta, es decir, la expresión:

static_cast<Derived const&>(*this) 

no debe invocar recursivamente operator Derived const&() const.

Deduzco esto de la presencia de la palabra clave de lo contrario que implica un orden de las reglas. La regla 2/ debe probarse antes de la regla 4/.

0

No se recomienda el uso de operadores de conversión implícitos. En C++ 11 puede agregar la palabra clave explicit no solo a constructores de argumento único, sino también a operadores de conversión. Para el código C++ 03, puede usar una función de conversión llamada explícitamente como self() o down_cast().

Además, parece que está utilizando la clase Base para CRTP, es decir, para habilitar el polimorfismo estático. Eso significa que usted tiene que saber en tiempo de compilación el que una clase particular Derived que está llamando. Por lo tanto, no debería tener que utilizar las referencias const Base& en ningún código público, excepto para implementar una interfaz CRTP.

En mis proyectos, que tienen una plantilla de clase enable_crtp:

#include <type_traits> 
#include <boost/static_assert.hpp> 

template 
< 
     typename Derived 
> 
class enable_crtp 
{ 
public: 
     const Derived& self() const 
     { 
       return down_cast(*this); 
     } 

     Derived& self() 
     { 
       return down_cast(*this); 
     } 

protected: 
     // disable deletion of Derived* through Base* 
     // enable deletion of Base* through Derived* 
     ~enable_crtp() 
     { 
       // no-op 
     } 

private: 
     // typedefs 
     typedef enable_crtp Base; 

     // cast a Base& to a Derived& (i.e. "down" the class hierarchy) 
     const Derived& down_cast(const Base& other) const 
     { 
       BOOST_STATIC_ASSERT((std::is_base_of<Base, Derived>::value)); 
       return static_cast<const Derived&>(other); 
     } 

     // cast a Base& to a Derived& (i.e. "down" the class hierarchy) 
     Derived& down_cast(Base& other) 
     { 
     // write the non-const version in terms of the const version 
     // Effective C++ 3rd ed., Item 3 (p. 24-25) 
     return const_cast<Derived&>(down_cast(static_cast<const Base&>(other))); 
     } 
}; 

Esta clase se deriva de forma privada a partir de ninguna ISomeClass clase base CRTP así:

template<typename Impl> 
class ISomeClass 
: 
    private enable_crtp<Impl> 
{ 
public: 
    // interface to be implemented by derived class Impl 
    void fun1() const 
    { 
     self().do_fun1(); 
    } 

    void fun2() 
    { 
     self().do_fun2() 
    } 

protected: 
    ~ISomeClass() 
    {} 
}; 

Las diversas clases derivadas pueden aplicar esta interfaz en su propia manera específica de esta manera:

class SomeImpl 
: 
    public ISomeClass<SomeImpl> 
{ 
public: 
    // structors etc. 

private: 
    // implementation of interface ISomeClass 

    friend class ISomeClass<SomeImpl>; 

    void do_fun1() const 
    { 
     // whatever 
    } 

    void do_fun2() 
    { 
     // whatever 
    } 

    // data representation 
    // ... 
}; 

El código externo que llama al fun1 de class SomeImpl se delegará en la versión const o no const apropiada de self() en el class enable_crtp y después de la down_casting se llamará a la implementación do_fun1. Con un compilador decente, todas las indirecciones deberían optimizarse completamente.

NOTA: los destructores protegidas de ISomeClass y enable_crtp hacer el código de seguridad contra los usuarios que tratan de eliminar SomeImpl* objetos a través de punteros de base.

Cuestiones relacionadas