Conversion operator implemented with static
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.
上一篇: 所有节点次要
下一篇: 转换运算符使用静态实现