考虑一个基于策略的智能指针类Ptr,只有一个策略可以防止在NULL状态下解除引用它(不知何故)。让我们考虑两种此类政策:
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 )
之类的原因,因为它会创建一个临时策略对象,这是被禁止的。至于我认为不是最优的明显解决方案:
问题是:
是否存在从一种类型到另一种类型的隐式构造函数的存在的静态测试,它不会创建这些类型的对象?
或者:
如何在调用策略时保留隐式构造的信息&#39;来自主持人类的构造函数&#39;构造吗
修改
我刚刚意识到第二个问题可以通过这样的私有隐式标记构造函数轻松回答:
#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;
}
然而,错误的可读性并不高,我们对政策提出了不必要的要求,因此第一个问题的答案更为可取。
答案 0 :(得分:4)
请参阅N4064针对std::pair
和std::tuple
采取的“完美初始化”方法,该方法涉及测试std::is_constructible<T, U>::value
和std::is_convertible<U, T>::value
如果两者都为真,则存在隐式转换,如果仅第一个为真,则转换是显式的。
解决方案是为构造函数定义两个重载,一个是隐式的,一个是explicit
,并使用SFINAE,这样最多只有一个重载是可行的。
答案 1 :(得分:0)
嗯,这花了我一些时间才意识到,但如果问题在于,我们无法创建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)
。
使用Jonathan Wakely提供的完美初始化技巧以及临时策略托管,我们可以通过以下方式解决它:
#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;
}