Consider a class of policy-based Ptr smart pointers with a single policy that will prevent it from being dereferenced in the NULL state (in some way). Let's look at 2 policies of this kind:
Since the NotNull policy NotNull more restrictive, we want to allow implicit conversions from Ptr< T, NoChecking > to Ptr< T, NotNull > , but not in the opposite direction. This should be explicit for safety. Please take a look at the following implementation:
#include <iostream> #include <type_traits> #include <typeinfo> struct NoChecking; struct NotNull; struct NoChecking{ NoChecking() = default; NoChecking( const NoChecking&) = default; explicit NoChecking( const NotNull& ) { std::cout << "explicit conversion constructor of NoChecking" << std::endl; } protected: ~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o }; struct NotNull{ NotNull() = default; NotNull( const NotNull&) = default; NotNull( const NoChecking& ) { std::cout << "explicit conversion constructor of NotNull" << std::endl; } protected: ~NotNull() {} }; template< typename T, class safety_policy > class Ptr : public safety_policy { private: T* pointee_; public: template < typename f_T, class f_safety_policy > friend class Ptr; //we need to access the pointee_ of other policies when converting //so we befriend all specializations of Ptr //implicit conversion operator template< class target_safety > operator Ptr<T, target_safety>() const { std::cout << "implicit conversion operator of " << typeid( *this ).name() << std::endl; static_assert( std::is_convertible<const safety_policy&, const target_safety&>::value, //What is the condition to check? This requires constructibility "Safety policy of *this is not implicitly convertible to target safety policy." ); //calls the explicit conversion constructor of the target type return Ptr< T, target_safety >( *this ); } //explicit conversion constructor template< class target_safety > explicit Ptr( const Ptr<T, target_safety>& other ) : safety_policy( other ), //this is an explicit constructor call and will call explicit constructors when we make Ptr() constructor implicit! pointee_( other.pointee_ ) { std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; } Ptr() = default; }; //also binds to temporaries from conversion operators void test_noChecking( const Ptr< int, NoChecking >& ) { } void test_notNull( const Ptr< int, NotNull >& ) { } int main() { Ptr< int, NotNull > notNullPtr; //enforcing not null value not implemented for clarity Ptr< int, NoChecking > fastPtr( notNullPtr ); //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking test_notNull ( fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull test_noChecking ( notNullPtr ); //should be ERROR - NotNull is explicitly convertible to NoChecking return 0; }
Living example
Code crashes when implicitly converting in both directions, which means that std::is_convertible fails, even if the classes have compatible constructors. Problems:
- Constructor overloads cannot be distinguished simply by an explicit keyword, so we need an explicit constructor and an implicit conversion operator (or vice versa) in the host class.
- An explicit constructor is better because any constructor will call explicit constructors from the initialization list, even if it is implicated.
- The implicit conversion operator cannot create objects of type policy because their destructor is protected. This is why
std::is_convertible fails if this is not the case, and therefore we cannot use something like boost::implicit_cast< const target_policy& >( *this ) in the conversion statement, since it would create a temporary policy object that is prohibited .
As for the obvious solutions, which, in my opinion, are not optimal:
- To make a public policy destructor - and the UB risk when running Ptr * into policy * and removing it? This is unlikely in the above example, but possible in a real application.
- Make the destructor public and use secure inheritance - I need the advanced interface provided by public inheritance.
The question arises:
Is there a static test for the existence of an implicit constructor from one type to another that does not create objects of these types?
Or alternatively:
How to save implicit construct information when calling policy constructors from the host class constructor?
EDIT:
I just realized that the second question can be easily answered using a private constructor with an implicit flag:
#include <iostream> #include <type_traits> #include <typeinfo> struct implicit_flag {}; struct NoChecking; struct NotNull; struct NoChecking{ NoChecking() = default; NoChecking( const NoChecking&) = default; protected: explicit NoChecking( const NotNull& ) { std::cout << "explicit conversion constructor of NoChecking" << std::endl; } ~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o }; struct NotNull{ NotNull() = default; NotNull( const NotNull&) = default; protected: NotNull( implicit_flag, const NoChecking& ) { std::cout << "explicit conversion constructor of NotNull" << std::endl; } ~NotNull() {} }; template< typename T, class safety_policy > class Ptr : public safety_policy { private: T* pointee_; public: template < typename f_T, class f_safety_policy > friend class Ptr; //we need to access the pointee_ of other policies when converting //so we befriend all specializations of Ptr //implicit conversion operator template< class target_safety > operator Ptr<T, target_safety>() const { std::cout << "implicit conversion operator of " << typeid( *this ).name() << std::endl; /*static_assert( std::is_convertible<const safety_policy&, const target_safety&>::value, //What is the condition to check? This requires constructibility "Safety policy of *this is not implicitly convertible to target safety policy." );*/ //calls the explicit conversion constructor of the target type return Ptr< T, target_safety >( implicit_flag(), *this ); } //explicit conversion constructor template< class target_safety > explicit Ptr( const Ptr<T, target_safety>& other ) : safety_policy( other ), //this is an explicit constructor call and will not preserve the implicity of conversion! pointee_( other.pointee_ ) { std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; } private: //internal implicit-flagged constructor caller that is called from implicit conversion operator template< class target_safety > Ptr( implicit_flag implicit, const Ptr<T, target_safety>& other ) : safety_policy( implicit, other ), //this constructor is required in the policy pointee_( other.pointee_ ) { std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; } public: Ptr() = default; }; //also binds to temporaries from conversion operators void test_noChecking( const Ptr< int, NoChecking >& ) { } void test_notNull( const Ptr< int, NotNull >& ) { } int main() { Ptr< int, NotNull > notNullPtr; //enforcing not null value not implemented for clarity Ptr< int, NoChecking > fastPtr( notNullPtr ); //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking test_notNull ( fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull test_noChecking ( notNullPtr ); //should be ERROR - NotNull is explicitly convertible to NoChecking return 0; }
Errors, however, are not very readable, and we introduce an unnecessary requirement for politicians, so the answer to the first question is more preferable.