简体   繁体   中英

Conversion operator implemented with static_cast

I ask this question following the issue I raised here .

The point is quite simple. Suppose you have two classes of this kind:

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

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

Then suppose you have a type conversion like this one:

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

The question is: what should be the standard conforming behavior of this conversion?

Should it be the same as const T & val(static_cast<const T &> (param) ) or should it recursively iterate until stack overflow? Notice that I obtain the first behavior compiling with GNU g++ and the second compiling with Intel icpc .

I already tried to peek at the standard (section 5.9 on static_cast and section 12.3 on conversions) but due to my lack of experience I was not able to figure out the answer.

My many thanks in advance to anybody taking the time to help me out with this.

Looking at [expr.static.cast] in n3337 (first working draft after the Standard):

2/ An lvalue of type “cv1 B ,” where B is a class type, can be cast to type “reference to cv2 D ,” where D is a class derived (Clause 10) from B , if a valid standard conversion from “pointer to D ” to “pointer to B ” exists [...]

4/ Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t [..]

Therefore, I would interpret that gcc's behavior is the correct one, ie the expression:

static_cast<Derived const&>(*this)

should not invoke recursively operator Derived const& () const .

I deduce this from the presence of the Otherwise keyword which implies an ordering of the rules. The rule 2/ should be tried before the rule 4/ .

The use of implicit conversion operators is not recommended. In C++11 you can add the keyword explicit not only to single argument constructors, but also to conversion operators. For C++03 code, you could use an explicitly named conversion function such as self() or down_cast() .

Furthermore, you seem to be using the Base class for CRTP, ie to enable static polymorphism. That means that you have to know at compile-time which particular Derived class you are calling. Therefore, you should not have to use const Base& references in any public code, except to implement a CRTP interface.

In my projects, I have a class template 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)));
        }
};

This class is privately derived from by any CRTP base class ISomeClass like this:

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

The various derived classes can implement this interface in their own specific way like this:

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

Outside code calling fun1 of class SomeImpl will get delegated to the appropriate const or non-const version of self() in the class enable_crtp and after down_casting the implementation do_fun1 will be called. With a decent compiler, all the indirections should be optimized away completely.

NOTE: the protected destructors of ISomeClass and enable_crtp make the code safe against users who try to delete SomeImpl* objects through base pointers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM