Рассмотрим класс Ptr интеллектуальных указателей на основе политик с единственной политикой, которая предотвратит разыменование его в состоянии NULL (каким-либо образом). Давайте рассмотрим 2 политики этого типа:
NotNull
NoChecking
Так как NotNull
политика является более ограничительным, мы хотели бы разрешить неявные преобразования из Ptr< T, NoChecking >
в Ptr< T, NotNull >
, но не в обратном направлении. Это должно быть явно для безопасности. Пожалуйста, взгляните на следующую реализацию:
#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;
}
Приведенный выше код дает сбой при неявном преобразовании в обоих направлениях, что означает, что std::is_convertible
не удается, даже если у классов есть совместимые конструкторы. Проблемы:
std::is_convertible
терпит неудачу, когда это не должно, и это также, почему мы не можем использовать что-то вроде boost::implicit_cast< const target_policy& >( *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;
}
Однако ошибки не очень читабельны, и мы вводим ненужные требования к политикам, поэтому ответ на первый вопрос предпочтительнее.
Смотрите подход «идеальной инициализации» N4064 за std::pair
а также std::tuple
который включает в себя тестирование std::is_constructible<T, U>::value
а также std::is_convertible<U, T>::value
Если оба являются истинными, существует неявное преобразование, если только первое является истинным, преобразование является явным.
Решение состоит в том, чтобы определить две перегрузки для конструктора, один неявный и один explicit
и используйте SFINAE, чтобы была возможна только одна перегрузка.
Ну, это заняло у меня некоторое время, чтобы понять, но если проблема заключается в том, что мы не можем создавать объекты типа policy
потому что его деструктор protected
, тогда почему бы нам не разместить его во временном классе пересылки?
Ptr оператор неявного преобразования:
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 );
}
Хитрость заключается в том, чтобы направить конструкторов target_policy
в target_host
так что это может быть построено из аргументов target_policy
Можно. поскольку Ptr
происходит от safety_policy
это может быть неявно преобразовано в (const) safety_policy&(&)
, Это означает, что тестирование конверсии Ptr -> target_host
эквивалентно испытательной конструкции target_host::target_safety(safety_policy)
,
Используя идеальный прием инициализации, предоставленный Джонатаном Уэйкли в сочетании с временным размещением политик, мы можем решить его следующим образом:
#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;
}