Reputation: 907
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:
std::is_convertible
fails when it shouldn't, and this is also why we can't use something like boost::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:
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.
Upvotes: 6
Views: 365
Reputation: 907
Well, that took me some time to realize, but if the problem lies in the fact, that we can't create objects of type policy
because its destructor is protected
, then why don't we host it in a temporary forwarding class?
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;
struct target_host : target_safety { using target_safety::target_safety; };
static_assert( std::is_convertible<Ptr, target_host>::value,
//Now this works, because target_host is constructible!
"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 );
}
The trick is to forward the constructors of target_policy
to target_host
, so it can be constructed from arguments that target_policy
can. Since Ptr
derives from safety_policy
it can be implicitly converted to (const) safety_policy&(&)
. This means that tesing conversion Ptr -> target_host
is equivalent to testing construction target_host::target_safety(safety_policy)
.
Using the perfect initialization trick provided by Jonathan Wakely in conjunction with temporary policy hosting we can solve it in the following way:
#include <iostream>
#include <type_traits>
#include <typeinfo>
template< typename Policy >
struct policy_host_
: Policy
{
using Policy::Policy;
};
template< typename Source, typename Target >
struct is_implicitly_convertible
: std::integral_constant<
bool
, std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value &&
std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value
>
{ };
template< typename Source, typename Target >
struct is_explicitly_convertible
: std::integral_constant<
bool
, std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value &&
!std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value
>
{ };
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
template<
class target_safety,
typename std::enable_if<
is_implicitly_convertible< target_safety, safety_policy >::value
, bool>::type = false
> Ptr( const Ptr<T, target_safety>& other )
: safety_policy( other ),
pointee_( other.pointee_ )
{ std::cout << "implicit Ptr constructor of " << typeid( *this ).name() << std::endl; }
template<
class target_safety,
typename std::enable_if<
is_explicitly_convertible< target_safety, safety_policy >::value
, bool>::type = false
> 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; }
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;
}
Upvotes: 0
Reputation: 171403
See the "perfect initialization" approach taken by N4064 for std::pair
and std::tuple
which involves testing std::is_constructible<T, U>::value
and std::is_convertible<U, T>::value
If both are true, there is an implicit conversion, if only the first is true the conversion is explicit.
The solution is to define two overloads for the constructor, one that is implicit and one explicit
, and use SFINAE so that at most one overload is viable.
Upvotes: 4