Consider a policy-based smart pointer class Ptr with only one policy that will prevent dereferencing it in a NULL state (somehow). Let's consider 2 policies of this kind:
NotNull
NoChecking
Since the NotNull
policy is more restrictive, we would like to allow implicit conversions from Ptr< T, NoChecking >
to Ptr< T, NotNull >
, but not in the opposite direction. That one has to 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's 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;
}
The code above fails when implicitly converting in both directions, which means that std::is_convertible
fails even though the classes have compatible constructors. The problems are:
- Constructor overloads cannot differ simply by explicit keyword, so we need an explicit constructor and implicit conversion operator (or vice versa) in the host class.
- Explicit constructor is better, because any constructor will call explicit constructors from initialization list even if is implicit itself.
- Implicit conversion operator cannot create objects of policy type because their destructor is protected. This is why
std::is_convertible
fails when it shouldn't, and this is also why we can't use something likeboost::implicit_cast< const target_policy& >( *this )
in the conversion operator, as it would create a temporary policy object, which is forbidden.
As for the obvious solutions that are not optimal in my opinion:
- Make the policy destructor public - and risk UB when casting the Ptr* to policy* and deleting it? This unlikely in the example provided, but is possible in real-world application.
- Make the destructor public and use protected inheritance - I need the enriched interface the public inheritance provides.
The question is:
Is there a static test for existence of implicit constructor from one type to another that does not create objects of these types?
Or alternatively:
How do I preserve the information of implicit construction when calling the policies' constructors from the host class' constructor?
EDIT:
I just realized that the second question can be easily answered with a private, implicit-flagged constructor like this:
#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's 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;
}
The errors however are not very readable and we introduce an unnecessary requirement to the policies, so an answer to the first question is more preferable.