使用 SFINAE 测试一个指针类型是否可以 static_cast 到另一个指针类型
Using SFINAE to test if a pointer type can be static_cast to another pointer type
背景
我正在写一个可移动的 QScopedPointer
;基本上 std::unique_pointer
有一些额外的访问器。我在访问兼容 C++11 的编译器之前就开始了它,但现在我决心把它做好(即使我正在重新发明轮子)。
问题
让我们调用我的智能指针MyUniquePointer
。
我需要知道类型 U *
是否可以通过 static_cast
转换为类型 T *
,具体来说:
template<class T, class Cleanup>
class MyUniquePointer
{
...
template<class U, class CleanupU, class = std::enable_if_t<detail::is_safely_castable<U, T>()>
MyUniquePointer(MyUniquePointer<U, CleanupU> && other) noexcept
: d(static_cast<T *>(other.release()))
{}
...
问题的前半部分
我的第一次尝试是在 enable_if
中使用 static_cast
,但是你不能使用 std::declval()
的地址来获取 static_cast
的指针!
有没有办法使用模板魔术测试指向 U
的指针是否可以 static_cast
指向 T
的指针?
尝试的解决方法
基于 cppreference and ,我尝试创建一个模板测试来模拟 static_cast 何时合法,如果向下转型, 安全 。这是我到目前为止整理的内容:
#include <iostream>
#include <type_traits>
template <class From, class To>
struct is_safely_castable //should probably be is_safely_castable_pointer or something
: std::integral_constant<bool,
std::is_pointer<From>() && std::is_pointer<To>()
&& ((std::is_base_of<To, From>()/* && std::has_virtual_destructor<From>()*/)
|| std::is_convertible<From, To>()
|| std::is_same<To,void *>()
|| std::is_same<From, void *>())>
{
};
struct base_type
{
base_type() = default;
base_type(base_type &&) = default;
base_type(const base_type &) = default;
virtual ~base_type() { }
base_type &operator=(const base_type &) = default;
base_type &operator=(base_type &&) = default;
};
struct derived_type : public base_type
{
};
struct unrelated_type
{
};
struct convertible_type
{
convertible_type(const base_type *) {}
convertible_type(base_type *) {}
convertible_type() = default;
operator base_type *() { return nullptr; }
};
int main(int argc, char *argv[])
{
(void)(argc);
(void)(argv);
base_type *b = new base_type;
derived_type *d = new derived_type;
unrelated_type *u = new unrelated_type;
uint32_t *i32 = new uint32_t{1};
uint64_t *i64 = new uint64_t{2};
void *v = static_cast<derived_type *>(d);
std::cout << std::boolalpha
<< "Base to base: " << (bool)static_cast<base_type *>(b) << '\n'
<< "Base to derived: " << (bool)static_cast<derived_type *>(b) << '\n'
<< "Derived to base: " << (bool)static_cast<base_type *>(d) << '\n'
<< "Unrelated to base: false\n" //<< static_cast<base_type *>(u) << '\n'
<< "uint32 to uint64: false\n" //<< static_cast<uint64_t *>(i32) << '\n'
<< "uint64 to uint32: false\n" //<< static_cast<uint32_t *>(i64) << '\n'
<< "Base to void: " << (bool)static_cast<void *>(b) << '\n'
<< "Void to derived: " << (bool)static_cast<derived_type *>(v) << '\n'
<< "Convertible to base: false\n" //<< static_cast<base_type *>(c) << '\n'
<< "Base to convertible: false\n";//<< static_cast<convertible_type *>(b) << '\n';
std::cout << "-----------\n"
<< "Base to base: " << is_safely_castable<base_type *, base_type *>() << '\n'
<< "Base to derived: " << is_safely_castable<base_type *, derived_type *>() << '\n'
<< "Derived to base: " << is_safely_castable<derived_type *, base_type *>() << '\n'
<< "Unrelated to base: " << is_safely_castable<unrelated_type *, base_type *>() << '\n'
<< "uint32 to uint64: " << is_safely_castable<uint32_t *, uint64_t *>() << '\n'
<< "uint64 to uint32: " << is_safely_castable<uint64_t *, uint32_t *>() << '\n'
<< "Base to void: " << is_safely_castable<base_type *, void *>() << '\n'
<< "Void to derived: " << is_safely_castable<void *, derived_type *>() << '\n'
<< "Convertible to base: " << is_safely_castable<convertible_type *, base_type *>() << '\n'
<< "Base to convertible: " << is_safely_castable<base_type *, convertible_type *>() << '\n';
delete b;
delete d;
delete u;
delete i32;
delete i64;
return 0;
}
哪个 returns:
Base to base: true
Base to derived: true
Derived to base: true
Unrelated to base: false
uint32 to uint64: false
uint64 to uint32: false
Base to void: true
Void to derived: true
Convertible to base: false
Base to convertible: false
-----------
Base to base: true
Base to derived: false
Derived to base: true
Unrelated to base: false
uint32 to uint64: false
uint64 to uint32: false
Base to void: true
Void to derived: true
Convertible to base: false
Base to convertible: false
我的问题的后半部分是此解决方法是否在正确的轨道上,更具体地说,|| std::is_convertible<From, To>()
是否应该
包括。通过时是否可以让 is_convertible
return 为真
指向类型的指针作为模板参数?上面的代码包括我自己笨拙的尝试让它工作。
脚注:我知道 base_type *
成功转换为 derived_type *
,但我不是编译器,不能做出这样的假设。
看来你想要:
template <typename T, typename U, typename = void>
struct is_safely_castable : std::false_type {};
template <typename T, typename U>
struct is_safely_castable<T, U,
std::void_t<decltype(static_cast<U>(std::declval<T>()))>>
: std::true_type
{};
背景
我正在写一个可移动的 QScopedPointer
;基本上 std::unique_pointer
有一些额外的访问器。我在访问兼容 C++11 的编译器之前就开始了它,但现在我决心把它做好(即使我正在重新发明轮子)。
问题
让我们调用我的智能指针MyUniquePointer
。
我需要知道类型 U *
是否可以通过 static_cast
转换为类型 T *
,具体来说:
template<class T, class Cleanup>
class MyUniquePointer
{
...
template<class U, class CleanupU, class = std::enable_if_t<detail::is_safely_castable<U, T>()>
MyUniquePointer(MyUniquePointer<U, CleanupU> && other) noexcept
: d(static_cast<T *>(other.release()))
{}
...
问题的前半部分
我的第一次尝试是在 enable_if
中使用 static_cast
,但是你不能使用 std::declval()
的地址来获取 static_cast
的指针!
有没有办法使用模板魔术测试指向 U
的指针是否可以 static_cast
指向 T
的指针?
尝试的解决方法
基于 cppreference and
#include <iostream>
#include <type_traits>
template <class From, class To>
struct is_safely_castable //should probably be is_safely_castable_pointer or something
: std::integral_constant<bool,
std::is_pointer<From>() && std::is_pointer<To>()
&& ((std::is_base_of<To, From>()/* && std::has_virtual_destructor<From>()*/)
|| std::is_convertible<From, To>()
|| std::is_same<To,void *>()
|| std::is_same<From, void *>())>
{
};
struct base_type
{
base_type() = default;
base_type(base_type &&) = default;
base_type(const base_type &) = default;
virtual ~base_type() { }
base_type &operator=(const base_type &) = default;
base_type &operator=(base_type &&) = default;
};
struct derived_type : public base_type
{
};
struct unrelated_type
{
};
struct convertible_type
{
convertible_type(const base_type *) {}
convertible_type(base_type *) {}
convertible_type() = default;
operator base_type *() { return nullptr; }
};
int main(int argc, char *argv[])
{
(void)(argc);
(void)(argv);
base_type *b = new base_type;
derived_type *d = new derived_type;
unrelated_type *u = new unrelated_type;
uint32_t *i32 = new uint32_t{1};
uint64_t *i64 = new uint64_t{2};
void *v = static_cast<derived_type *>(d);
std::cout << std::boolalpha
<< "Base to base: " << (bool)static_cast<base_type *>(b) << '\n'
<< "Base to derived: " << (bool)static_cast<derived_type *>(b) << '\n'
<< "Derived to base: " << (bool)static_cast<base_type *>(d) << '\n'
<< "Unrelated to base: false\n" //<< static_cast<base_type *>(u) << '\n'
<< "uint32 to uint64: false\n" //<< static_cast<uint64_t *>(i32) << '\n'
<< "uint64 to uint32: false\n" //<< static_cast<uint32_t *>(i64) << '\n'
<< "Base to void: " << (bool)static_cast<void *>(b) << '\n'
<< "Void to derived: " << (bool)static_cast<derived_type *>(v) << '\n'
<< "Convertible to base: false\n" //<< static_cast<base_type *>(c) << '\n'
<< "Base to convertible: false\n";//<< static_cast<convertible_type *>(b) << '\n';
std::cout << "-----------\n"
<< "Base to base: " << is_safely_castable<base_type *, base_type *>() << '\n'
<< "Base to derived: " << is_safely_castable<base_type *, derived_type *>() << '\n'
<< "Derived to base: " << is_safely_castable<derived_type *, base_type *>() << '\n'
<< "Unrelated to base: " << is_safely_castable<unrelated_type *, base_type *>() << '\n'
<< "uint32 to uint64: " << is_safely_castable<uint32_t *, uint64_t *>() << '\n'
<< "uint64 to uint32: " << is_safely_castable<uint64_t *, uint32_t *>() << '\n'
<< "Base to void: " << is_safely_castable<base_type *, void *>() << '\n'
<< "Void to derived: " << is_safely_castable<void *, derived_type *>() << '\n'
<< "Convertible to base: " << is_safely_castable<convertible_type *, base_type *>() << '\n'
<< "Base to convertible: " << is_safely_castable<base_type *, convertible_type *>() << '\n';
delete b;
delete d;
delete u;
delete i32;
delete i64;
return 0;
}
哪个 returns:
Base to base: true
Base to derived: true
Derived to base: true
Unrelated to base: false
uint32 to uint64: false
uint64 to uint32: false
Base to void: true
Void to derived: true
Convertible to base: false
Base to convertible: false
-----------
Base to base: true
Base to derived: false
Derived to base: true
Unrelated to base: false
uint32 to uint64: false
uint64 to uint32: false
Base to void: true
Void to derived: true
Convertible to base: false
Base to convertible: false
我的问题的后半部分是此解决方法是否在正确的轨道上,更具体地说,|| std::is_convertible<From, To>()
是否应该
包括。通过时是否可以让 is_convertible
return 为真
指向类型的指针作为模板参数?上面的代码包括我自己笨拙的尝试让它工作。
脚注:我知道 base_type *
成功转换为 derived_type *
,但我不是编译器,不能做出这样的假设。
看来你想要:
template <typename T, typename U, typename = void>
struct is_safely_castable : std::false_type {};
template <typename T, typename U>
struct is_safely_castable<T, U,
std::void_t<decltype(static_cast<U>(std::declval<T>()))>>
: std::true_type
{};