7

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.

Community
  • 1
  • 1
Massimiliano
  • 7,842
  • 2
  • 47
  • 62
  • 2
    The relevant section would be the one on overload resolution. – R. Martinho Fernandes Mar 22 '12 at 09:23
  • If you'd humour my curiousity - why do you want the conversion operator in `Base`? – Tony Delroy Mar 22 '12 at 09:43
  • 1
    @ Tony Delroy: 1) to be less verbose than explicitly calling a class method.2) I found this kind of code a couple of time in the web while trying to get some knowledge on CRTPs, see for instance [here](http://en.wikipedia.org/wiki/Expression_templates) 3) After finding this strange behavior I got really curious on which should be the correct one :-) – Massimiliano Mar 22 '12 at 09:57

2 Answers2

3

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

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

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, i.e. 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.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304