Maintaining the implicability of building in the political class

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:

  • NotNull
  • NoChecking

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.

+6
source share
2 answers

See the N4064 "perfect initialization" approach for std::pair and std::tuple , which includes testing std::is_constructible<T, U>::value and std::is_convertible<U, T>::value

If both values ​​are true, an implicit conversion occurs; if only the first is true, the conversion is explicit.

The solution is to define two overloads for the constructor: one implicit and one explicit and use SFINAE to provide the maximum possible overload.

+4
source

Well, it took me some time to understand, but if the problem is that we cannot create objects of type policy , because its destructor is protected , then why don't we put it in a temporary forwarding class?

Implicit Ptr 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 safety policy." ); //calls the explicit conversion constructor of the target type return Ptr< T, target_safety >( *this ); } 

Live demo

The trick is to forward constructors from target_policy to target_host , so it can be built from arguments, which can be target_policy . Since Ptr comes from safety_policy , it can be implicitly converted to (const) safety_policy&(&) . This means that the tesing Ptr -> target_host equivalent to testing the target_host::target_safety(safety_policy) .


Using the perfect initialization trick provided by Jonathan Wackley in combination with temporary hosting, we can solve it as follows:

 #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; } 

Live demo

0
source

Source: https://habr.com/ru/post/974419/


All Articles