C++98/03 std::is_constructible implementation
C++98/03 std::is_constructible implementation
我的爱好库的基本组件必须与c++ 98和c++ 11编译器一起工作。为了学习和享受自己,我创建了几个类型支持功能的c++ 98实现(如enable_if
, conditional
, is_same
, is_integral
等. ...),以便在没有c++ 11支持时使用它们。
然而,当我实现is_constructible
我卡住了。是否有任何类型的模板魔术(某种SFINAE),我可以实现它没有c++ 11的支持(declval
)?
当然在c++ 03中没有可变模板支持,所以我将专门研究实现直到一些深度。主要的问题是,是否有一种技术可以决定T是否可以从给定的类型构造。
这是可能的:
#include <iostream>
template<typename T, T Val>
struct integral_constant {
typedef integral_constant type;
typedef T value_type;
enum {
value = Val
};
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
template<typename T>
struct remove_ref {
typedef T type;
};
template<typename T>
struct remove_ref<T&> {
typedef T type;
};
// is_base_of from https://stackoverflow.com/questions/2910979/how-does-is-base-of-work
namespace aux {
typedef char yes[1];
typedef char no[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
}
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static aux::yes& check(D*, T);
static aux::no& check(B*, int);
static const bool value = sizeof(check(aux::Host<B,D>(), int())) == sizeof(aux::yes);
};
template<typename T>
struct remove_cv {
typedef T type;
};
template<typename T>
struct remove_cv<const T> {
typedef T type;
};
template<typename T>
struct remove_cv<volatile T> {
typedef T type;
};
template<typename T>
struct remove_cv<const volatile T> {
typedef T type;
};
template<typename T>
struct is_void : integral_constant<bool, false> {};
template<>
struct is_void<void> : integral_constant<bool, true> {};
template<class T>
struct type_identity {
// Used to work around Visual C++ 2008's spurious error: "a function-style conversion to a built-in type can only take one argument"
typedef T type;
};
template <bool, typename T, typename>
struct conditional {
typedef T type;
};
template <typename T, typename U>
struct conditional<false, T, U> {
typedef U type;
};
namespace aux {
template<typename T, typename U>
struct is_more_const : integral_constant<bool, false> {};
template<typename T, typename U>
struct is_more_const<const T, U> : integral_constant<bool, true> {};
template<typename T, typename U>
struct is_more_const<const T, const U> : integral_constant<bool, false> {};
template<typename T, typename U>
struct is_more_volatile : integral_constant<bool, false> {};
template<typename T, typename U>
struct is_more_volatile<volatile T, U> : integral_constant<bool, true> {};
template<typename T, typename U>
struct is_more_volatile<volatile T, volatile U> : integral_constant<bool, false> {};
template<typename T, typename U>
struct is_more_cv : integral_constant<bool, is_more_const<T,U>::value && is_more_volatile<T,U>::value> {};
template<typename T>
struct is_default_constructible {
template<typename U>
static yes& test(int(*)[sizeof(new U)]);
template<typename U>
static no& test(...);
enum {
value = sizeof(test<T>(0)) == sizeof(yes)
};
};
template<typename T, typename Arg>
struct is_constructible_1 {
template<typename U, typename Arg_>
static yes& test(int(*)[sizeof(typename type_identity<U>::type(static_cast<Arg_>(*((typename remove_ref<Arg_>::type*)0))))]);
template<typename U, typename Arg_>
static no& test(...);
enum {
value = sizeof(test<T, Arg>(0)) == sizeof(yes)
};
};
// Base pointer construct from Derived Pointer
template<typename T, typename U>
struct is_constructible_1<T*, U*>
: conditional<
is_void<typename remove_cv<T>::type>::value,
integral_constant<bool, true>,
typename conditional<
is_void<typename remove_cv<U>::type>::value,
integral_constant<bool, false>,
typename conditional<
is_more_cv<T, U>::value,
integral_constant<bool, false>,
is_base_of<T,U>
>::type
>::type
>::type
{};
// Base pointer construct from Derived Pointer
template<typename T, typename U>
struct is_constructible_1<T&, U&>
: conditional<
is_more_cv<T, U>::value,
integral_constant<bool, false>,
is_base_of<T,U>
>::type
{};
template<typename T, typename Arg1, typename Arg2>
struct is_constructible_2 {
template<typename U, typename Arg1_, typename Arg2_>
static yes& test(int(*)[
sizeof(typename type_identity<U>::type(
static_cast<Arg1_>(*((typename remove_ref<Arg1_>::type*)0)),
static_cast<Arg2_>(*((typename remove_ref<Arg2_>::type*)0))
))
]);
template<typename U, typename Arg1_, typename Arg2_>
static no& test(...);
enum {
value = sizeof(test<T, Arg1, Arg2>(0)) == sizeof(yes)
};
};
}
template<typename T, typename Arg1 = void, typename Arg2 = void>
struct is_constructible : integral_constant<bool, aux::is_constructible_2<T, Arg1, Arg2>::value> {
};
template<typename T, typename Arg>
struct is_constructible<T, Arg> : integral_constant<bool, aux::is_constructible_1<T, Arg>::value> {
};
template<typename T>
struct is_constructible<T> : integral_constant<bool, aux::is_default_constructible<T>::value> {
};
struct Foo {};
struct fuzz_explicit {};
struct fuzz_implicit {};
struct Fuzz {
explicit Fuzz(fuzz_explicit);
Fuzz(fuzz_implicit);
};
struct buzz_explicit {};
struct buzz_implicit {};
struct Buzz {
explicit Buzz(buzz_explicit);
Buzz(buzz_implicit);
};
struct Bar {
Bar(int);
Bar(int, double&);
Bar(Fuzz);
explicit Bar(Buzz);
};
struct Base {};
struct Derived : Base {};
#define TEST(X) std::cout << #X << X << 'n'
int main() {
TEST((is_constructible<Foo>::value));
TEST((is_constructible<Bar>::value));
TEST((is_constructible<Foo, int>::value));
TEST((is_constructible<Bar, int>::value));
TEST((is_constructible<Foo, const Foo&>::value));
TEST((is_constructible<Bar, Bar>::value));
TEST((is_constructible<Bar, int, double>::value));
TEST((is_constructible<Bar, int, double&>::value));
TEST((is_constructible<Bar, int, const double&>::value));
TEST((is_constructible<int*, void*>::value));
TEST((is_constructible<void*, int*>::value));
TEST((is_constructible<Base&, Derived&>::value));
TEST((is_constructible<Derived*, Base*>::value));
// via Fuzz
TEST((is_constructible<Bar, fuzz_explicit>::value));
TEST((is_constructible<Bar, fuzz_implicit>::value));
// via Buzz
TEST((is_constructible<Bar, buzz_explicit>::value));
TEST((is_constructible<Bar, buzz_implicit>::value));
// integer promotion
TEST((is_constructible<Bar, char>::value));
// integer conversion
TEST((is_constructible<Bar, unsigned long>::value));
}
可以将2参数版本扩展为3,4,5,…
现场演示
这适用于g++ 4.4.7
不支持g++ 4.3.6
我觉得Danh的主意很棒!稍加修改,我们就可以去掉operator new。(我有一个c++ 98 enable_if和remove_reference实现)。上面提到的int*, void*的情况也适用于这个实现。不需要新的操作员。只有旧的g++支持保留…
/********** std::remove_cv replacement **********/
template< typename T >
struct remove_const
{
typedef T type;
};
template< typename T >
struct remove_const< const T >
{
typedef T type;
};
template< typename T >
struct remove_volatile
{
typedef T type;
};
template< typename T >
struct remove_volatile< volatile T >
{
typedef T type;
};
template< typename T >
struct remove_cv
{
typedef typename remove_volatile< typename remove_const< T >::type >::type type;
};
/********** std::is_pointer replacement *********/
template< typename T >
struct is_pointer_helper
{
static const bool value = false;
};
template< typename T >
struct is_pointer_helper< T* >
{
static const bool value = true;
};
template< typename T >
struct is_pointer
{
static const bool value = is_pointer_helper< typename remove_cv< T >::type >::value;
};
/********** std::enable_if replacement **********/
template< bool CONDITION, typename TYPE = void >
struct enable_if
{
};
template< typename TYPE >
struct enable_if< true, TYPE >
{
typedef TYPE type;
};
/****** std::remove_reference replacement *******/
template< typename T >
struct remove_reference
{
typedef T type;
};
template< typename T >
struct remove_reference< T& >
{
typedef T type;
};
/******* std::is_constructible replacement ******/
template< typename T, typename AT_1 = void, typename AT_2 = void, typename AT_3 = void, typename AT_4 = void >
class is_constructible_impl
{
private:
template< typename C_T, typename C_AT_1, typename C_AT_2, typename C_AT_3, typename C_AT_4 >
static bool test(
typename c_std::enable_if<
sizeof( C_T ) ==
sizeof( C_T(
static_cast< C_AT_1 >( *static_cast< typename c_std::remove_reference< C_AT_1 >::type* >( NULL ) ),
static_cast< C_AT_2 >( *static_cast< typename c_std::remove_reference< C_AT_2 >::type* >( NULL ) ),
static_cast< C_AT_3 >( *static_cast< typename c_std::remove_reference< C_AT_3 >::type* >( NULL ) ),
static_cast< C_AT_4 >( *static_cast< typename c_std::remove_reference< C_AT_4 >::type* >( NULL ) )
) )
>::type*
);
template< typename, typename, typename, typename, typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T, AT_1, AT_2, AT_3, AT_4 >( NULL ) ) == sizeof( bool ) );
};
template< typename T, typename AT_1, typename AT_2, typename AT_3 >
class is_constructible_impl< T, AT_1, AT_2, AT_3, void >
{
private:
template< typename C_T, typename C_AT_1, typename C_AT_2, typename C_AT_3 >
static bool test(
typename c_std::enable_if<
sizeof( C_T ) ==
sizeof( C_T(
static_cast< C_AT_1 >( *static_cast< typename c_std::remove_reference< C_AT_1 >::type* >( NULL ) ),
static_cast< C_AT_2 >( *static_cast< typename c_std::remove_reference< C_AT_2 >::type* >( NULL ) ),
static_cast< C_AT_3 >( *static_cast< typename c_std::remove_reference< C_AT_3 >::type* >( NULL ) )
) )
>::type*
);
template< typename, typename, typename, typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T, AT_1, AT_2, AT_3 >( NULL ) ) == sizeof( bool ) );
};
template< typename T, typename AT_1, typename AT_2 >
class is_constructible_impl< T, AT_1, AT_2, void, void >
{
private:
template< typename C_T, typename C_AT_1, typename C_AT_2 >
static bool test(
typename c_std::enable_if<
sizeof( C_T ) ==
sizeof( C_T(
static_cast< C_AT_1 >( *static_cast< typename c_std::remove_reference< C_AT_1 >::type* >( NULL ) ),
static_cast< C_AT_2 >( *static_cast< typename c_std::remove_reference< C_AT_2 >::type* >( NULL ) )
) )
>::type*
);
template< typename, typename, typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T, AT_1, AT_2 >( NULL ) ) == sizeof( bool ) );
};
template< typename T, typename AT_1 >
class is_constructible_impl< T, AT_1, void, void, void >
{
private:
template< typename C_T, typename C_AT_1 >
static bool test(
typename c_std::enable_if<
sizeof( C_T ) ==
sizeof( C_T(
static_cast< C_AT_1 >( *static_cast< typename c_std::remove_reference< C_AT_1 >::type* >( NULL ) )
) )
>::type*
);
template< typename, typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T, AT_1 >( NULL ) ) == sizeof( bool ) );
};
template< typename T >
class is_constructible_impl< T, void, void, void, void >
{
private:
template< typename C_T >
static C_T testFun( C_T );
template< typename C_T >
static bool test( typename c_std::enable_if< sizeof( C_T ) == sizeof( testFun( C_T() ) ) >::type* );
template< typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T >( NULL ) ) == sizeof( bool ) );
};
template< typename T, typename AT_1 = void, typename AT_2 = void, typename AT_3 = void, typename AT_4 = void >
class is_constructible_impl_ptr
{
public:
static const bool value = false;
};
template< typename T, typename AT_1 >
class is_constructible_impl_ptr< T, AT_1, typename enable_if< is_pointer< typename remove_reference< T >::type >::value, void >::type, void, void >
{
private:
template< typename C_T >
static bool test( C_T );
template< typename >
static int test( ... );
public:
static const bool value = ( sizeof( test< T >( static_cast< AT_1 >( NULL ) ) ) == sizeof( bool ) );
};
template< typename T >
class is_constructible_impl_ptr< T, void, void, void, void >
{
public:
static const bool value = true;
};
template< typename T, typename AT_1 = void, typename AT_2 = void, typename AT_3 = void, typename AT_4 = void >
class is_constructible
{
public:
static const bool value = (
is_pointer< typename remove_reference< T >::type >::value ?
is_constructible_impl_ptr< T, AT_1, AT_2, AT_3, AT_4 >::value :
is_constructible_impl< T, AT_1, AT_2, AT_3, AT_4 >::value
);
};
要实现完全符合 is_constructible
的,编译器的支持是必要的。问题不在于可变模板模拟或选择习惯用法(sizeof over decltype)。
实际上在gcc 8.x(4. x)之前。x到7.x), is_constructible<To, From>
上有一个bug,因为它完全是由库代码实现的。当To
是引用类型(即:T&
或T&&
)。这同样适用于clang libc++的库版本__libcpp_is_constructible<To, From>
,但clang从支持c++11开始就有了对__is_constructible()
的编译器支持,所以这从来都不是一个真正的问题。
不符合的情况是,在构造引用时,clang(libc++)和gcc(libstdc++)使用的纯库实现使用SFINAE检查static_cast<To>(declval<From>())
是否格式良好。但是在两种情况下,您必须显式地使用cast
而不是初始化语法(即:T t(args...)
):
- 当从基类的引用强制转换为派生类的引用时:
static_cast<Derived&>(declval<Base&>())
是有效的,但你必须总是显式地使用cast,即Base& bref; Derived& dref = bref;
不起作用,你必须使用Derived& dref = static_cast<Derived&>(bref)
。 - 当从左值引用转换为右值引用时:
static_cast<A&&>(declval<A&>())
是有效的(您熟悉的std::move()),但您必须始终显式地使用cast,即A& lref; A&& ref = lref;
不起作用,您必须使用A&& ref = static_cast<A&&>(lref);
(即A&& ref = std::move(lref);
)
为了解决这种误报,除了SFINAE转换检查外,在libc++和libstdc++中已经存在额外的检查,以确保强制转换不是上述两种情况。
但这引入了一个新问题:如果存在用户定义的(显式)转换,则__is_constructible()
是有效的。但是当转换也是上述情况之一时,就会发生假阴性。
Base&
to D1&
或D2&
需要显式转换,但是,也有一个用户定义的显式转换,将Base(&)
转换为D1&
。因此,is_constructible<D1&, Base&>::value
的计算结果为真,而is_constructible<D2&, Base&>::value
的计算结果为假。
struct D1;
struct D2;
struct Base {
explicit operator D1&();
};
struct D1 : Base {
D1(const D1&) = delete;
};
struct D2 : Base {};
int BtoD1() { // should be true
return std::is_constructible<D1&, Base&>::value;
}
int BtoD2() { // should be false
return std::is_constructible<D2&, Base&>::value;
}
但是标准库实现都报告为false。戈德波特·林克,你自己试试吧。您可以在clang/gcc(<7)/gcc(>=8)之间切换,看看结果是如何变化的。
上面的答案太棒了。但是,对于新手来说可能很难理解。
这是一个非常简单的解决方案,尽管它牺牲了大部分可移植性。
#include <cctype>
template<typename T>
struct is_default_constructible {
template<typename U>
static int8_t test(int(*)[sizeof(new U)]);
template<typename U>
static int16_t test(...);
enum {
value = sizeof(test<T>(0)) == 1
};
};
这是一个演示
class Test1 {
public:
Test1() = delete;
};
class Test2 {
public:
Test2();
};
int main() {
std::cout << is_default_constructible<Test1>::value
<< is_default_constructible<Test2>::value;
}