C ++ 98/03 std :: is_constructible implementation

时间:2016-11-05 15:25:28

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

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

然而,当我实施is_constructible时,我陷入困境。是否存在任何模板魔法(某种SFINAE),我可以在没有C ++ 11支持的情况下实现它(declval)?

当然在C ++ 03中没有可变参数模板支持,所以我将把实现专门化到一定深度。主要问题是,是否有一种技术可以决定T是否可以从给定类型中构造出来。

4 个答案:

答案 0 :(得分:7)

可能:

#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

答案 1 :(得分:4)

我认为Danh的想法很棒!通过微小的修改,我们可以消除操作员新的。 (我有一个C ++ 98 enable_if和remove_reference实现)。提到的int *,void * case也适用于此实现。无需新的操作员。只有旧的g ++支持仍然存在......

{{1}}

答案 2 :(得分:1)

要实现完全符合is_constructible,需要编译器支持。问题不在于可变参数模板模拟选择习惯用法(sizeof over decltype)

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

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

  1. 当从基类的引用转换为派生类的引用时:
    static_cast<Derived&>(declval<Base&>()) 是有效的,但您必须始终明确使用强制转换,即 Base& bref; Derived& dref = bref; 不起作用,您必须使用 Derived& dref = static_cast<Derived&>(bref)
  2. 当从左值引用转换为右值引用时:
    static_cast<A&&>(declval<A&>()) 是有效的(您熟悉的 std::move()),但您必须始终明确使用强制转换,即 A& lref; A&& ref = lref; 不起作用,您必须使用 A&& ref = static_cast<A&&>(lref);(即 {{1 }})

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

但这引入了一个新问题:如果存在用户定义的(显式)转换,则 A&& ref = std::move(lref); 是有效的。但是当转化也是上述情况之一时,就会出现假阴性。

例如,下面的代码演示了从基础到派生引用的转换场景。将 __is_constructible() 转换为 Base&D1& 需要显式转换,但是,还有一个用户定义的显式转换可以将 D2& 转换为 Base(&)。所以 D1& 的计算结果为 true,而 is_constructible<D1&, Base&>::value 的计算结果为 false。

is_constructible<D2&, Base&>::value

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

答案 3 :(得分:0)

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

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

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