【问题标题】:C++98/03 std::is_constructible implementationC++98/03 std::is_constructible 实现
【发布时间】:2016-11-05 15:25:28
【问题描述】:

我的爱好库的基本组件必须与 C++98 和 C++11 编译器一起使用。为了学习和享受自己,我创建了几种类型支持功能的 C++98 实现(如enable_ifconditionalis_sameis_integral 等......)以便在有不支持 C++11。

但是,当我在实施 is_constructible 时,我被卡住了。是否有任何类型的模板魔法(某种 SFINAE)我可以在没有 C++11 支持的情况下实现它(declval)?

当然,C++03 中不支持可变参数模板,所以我将专门研究实现,直到深入。主要问题是是否有一种技术可以确定 T 是否可以从给定类型构造。

【问题讨论】:

  • 一些编译器使用内置的 __is_constructible 帮助器,所以至少它可能非常困难。
  • 是的,C++11 的实现很清晰。我想用某种魔法从引用的实现中消除decltypestd::declval 依赖项,或者找到一种完全不同的技术。唯一的限制是 C++98 标准。

标签: c++ c++11 sfinae c++03 compile-time


【解决方案1】:

有可能:

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

您可以进一步扩展 3、4、5、... 参数的 2 参数版本。

Live Demo


这适用于g++ 4.4.7

它不适用于 g++ 4.3.6

【讨论】:

  • 您的版本隐式使用了表达式 SFINAE。虽然 C++03 似乎没有(明确地?)不允许它,但许多实现在过去并不支持它。所以不是一个真正令人满意的解决方案。
  • @Columbo 它适用于 g++ 4.4.7,但是是的,它不适用于 g++ 4.3.6
  • 类删除分配函数怎么办?如果我问is_constructible&lt;int*, void*&gt;怎么办?
  • @W.F.就像 Columbo 说的,这隐含地使用了表达式 SFINAE,C++03 没有明确禁止它,但是 C++11 允许它,在 C++11 中,这种检查可以检查私有访问
  • 在给定enum X { }; 的情况下,is_constructible&lt;X, int&gt; 仍然失败。也适用于is_constructible&lt;const Bar&amp;, Fuzz&amp;&gt;,以及指向成员的指针。这就是为什么is_constructible 是由内在函数实现的。
【解决方案2】:

我认为 Danh 的想法很棒!稍作修改,我们就可以消除运算符 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
    );
};

【讨论】:

  • 不错,但是当构造函数为私有 example 时我仍然遇到严重错误
  • 嗯,这听起来很有趣,因为我也检查过这个案例。你用什么编译器?
  • @Broothy 它为默认构造提供了不正确的值,请参阅melpon.org/wandbox/permlink/hrTNgb0LrXJyBPnY
  • @Broothy 该示例使用 gcc 4.4.7,但它也无法在 gcc 4.5.2、4.6.4、4.7 上编译,gcc 从 4.8 开始编译它。每个版本中的叮当声也失败了......
  • 你说得对,我没有用 VS 编译器检查我的实现。我会检查并尝试修复它。
【解决方案3】:

要实现完全符合 is_constructible,编译器支持是必要的。问题不在于 variadic template simulationselect idiom(sizeof over decltype)

其实在 gcc 8.x(4.x to 7.x) 之前,is_constructible&lt;To, From&gt; 上就有一个 bug,因为它纯粹是由库代码实现的。当To 是引用类型(即T&amp;T&amp;&amp;)时,就会出现该错误。这同样适用于 clang libc++ 的库版本__libcpp_is_constructible&lt;To, From&gt;,但由于 c++11 的支持,clang 对 __is_constructible() 有编译器支持,所以这从来都不是真正的问题。

不符合的情况是,在构造引用时,clang(libc++) 和 gcc(libstdc++) 使用的纯库实现使用 SFINAE 检查static_cast&lt;To&gt;(declval&lt;From&gt;()) 是否格式正确。但是有两种情况你必须显式使用cast而不是初始化语法(即T t(args...)):

  1. 从基类引用转换为派生类引用时:
    static_cast&lt;Derived&amp;&gt;(declval&lt;Base&amp;&gt;()) 有效,但必须始终显式使用转换,即Base&amp; bref; Derived&amp; dref = bref; 不起作用,必须使用Derived&amp; dref = static_cast&lt;Derived&amp;&gt;(bref) .
  2. 当从左值引用转换为右值引用时:
    static_cast&lt;A&amp;&amp;&gt;(declval&lt;A&amp;&gt;()) 有效(您熟悉的 std::move()),但您必须始终显式使用转换,即 A&amp; lref; A&amp;&amp; ref = lref; 不起作用,你必须使用A&amp;&amp; ref = static_cast&lt;A&amp;&amp;&gt;(lref);(即A&amp;&amp; ref = std::move(lref);

为了解决此类误报,除了 SFINAE 转换检查外,libc++ 和 libstdc++ 中已经存在额外检查,以确保强制转换不是上述两种情况。

但这引入了一个新问题:如果存在用户定义的(显式)转换,__is_constructible() 是有效的。但是当转换也是上述场景之一时,就会发生假阴性。

例如,下面的代码演示了基础到派生参考的转换场景。将Base&amp; 转换为D1&amp;D2&amp; 需要显式转换,但是,还有一个用户定义的显式转换将Base(&amp;) 转换为D1&amp;。所以is_constructible&lt;D1&amp;, Base&amp;&gt;::value 的计算结果为真,而is_constructible&lt;D2&amp;, Base&amp;&gt;::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;
}

但库实现将两者都报告为错误。 godbolt link 自己试试。你可以在 clang / gcc(=8) 之间切换,看看结果如何变化。

【讨论】:

    【解决方案4】:

    上面的答案太棒了。但是,新手可能难以理解

    这是一个非常简单的解决方案,尽管它牺牲了大部分的可移植性。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-05
      • 1970-01-01
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 2022-06-13
      相关资源
      最近更新 更多