开发者

Is there a way to test whether a C++ class has a default constructor (other than compiler-provided type traits)?

开发者 https://www.devze.com 2022-12-28 18:12 出处:网络
Traits classes can be defined to check if a C++ class has a member variable, function or a type (see here).

Traits classes can be defined to check if a C++ class has a member variable, function or a type (see here).

Curiously, the ConceptTraits do not include traits to check if a C++ class defines a default constructor or given con开发者_JS百科structor?

Can traits be used to check the constructor presence? If yes, how? If not, why it is not possible?


Sorry for answering may own question.

Googling I have found that the actual reason we can not check if a class has constructor or a destructors is that, the known technique used to detect if a class has a member is based on taking the address of the member. But constructors and destructors have no name, we can not take the address of them.

If we can not take the address, I don't see a way to make the compiler react to a construction without instantiating it directly, but in this case there is no detection at compile time but an error.

So to answer my own question, I would say that with the current techniques it is not possible to detect them and compiler support is needed. But C++ has revealed a lot of surprises, and things that were not possible at a given time, were revealed are possible using another technique.

I hope a C++ language expert is reading that and can give a more clear explanation.


Concept Traits is no more maintained, but become part of Type Traits. And in docs of has_trivial_constructor and has_trivial_destructor, Boost authors explain clearly that compiler support is required to make this work.


A modification of Potatoswatter's answer

Works on gcc-4.6

#include <type_traits>

template< class T >
struct identity {
typedef T type;
};

template<bool, typename T, typename F>
struct if_c : identity<F> {};

template< typename T, typename F>
struct if_c<true,T,F> : identity<T> {};

template<typename Bool, typename T, typename F>
struct if_ : if_c< Bool::value, T, F> {};

template< class T >
struct is_default_constructible_;

template< class T >
struct is_default_constructible :
  if_< std::is_arithmetic<T>,
    std::true_type,
    is_default_constructible_<T> >::type { };


template< class T >
struct is_default_constructible_ {

    template<class D> class Acessible : public D
    {
      friend class is_default_constructible_<D>;
      public:
      //using D::D; may be needed once N2540 is implemented 
    };

    template<int x>
    class receive_size{};

    template< class U >
    static int sfinae( receive_size< sizeof Acessible<U>() > * );

    template< class U >
    static char sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(int) };

};

struct p { p(); };
class q { q(); };
class r { r(int); };

#include <iostream>
using namespace std;

int main() {
    cerr << is_default_constructible<int>::value << endl // outputs 1
        << is_default_constructible<p>::value << endl
        << is_default_constructible<q>::value << endl
        << is_default_constructible<r>::value << endl; // outputs 0
}

# g++-mp-4.6 --std=c++0x -Wall test.cpp && ./a.out
1
1
0
0


Warning: some analysis below is obsolete as of C++11. In C++11, access checking is done prior to instantiation and access violation is-not-an-error. Therefore the attached code may be more compliant. I haven't re-analyzed it.


I'm pretty new to SFINAE. Today it occurred to me to put a test expression inside a sizeof inside a template parameter in a function argument type.

According to N2634 this is not wrong, but highly unportable. (EDIT: appears to be compliant to C++0x FCD.) It can only return positive or fail to compile in GCC 4.2; GCC 4.5 scores a 3 out of 3 for my test cases.

The SFINAE rules were broadened (in this case) since C++03 in the FCD. New §14.8.2/8 (emphasis mine):

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. Access checking is not done as part of the substitution process. Consequently, when deduction succeeds, an access error could still result when the function is instantiated. Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed.

template< class T >
class is_default_constructible {
    template<int x>
    class receive_size{};

    template< class U >
    static int sfinae( receive_size< sizeof U() > * );

    template< class U >
    static char sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(int) };
};

class q { q(); };
class r { r(int); };

#include <iostream>
using namespace std;

int main() {
    cerr << is_default_constructible<int>::value << endl // outputs 1
        // fails to compile: access violation
        // FCD demands that access violations be unrecoverable
        // indeed, it's murky: q is default-constructible, but only "rarely"
        //<< is_default_constructible<q>::value << endl
        << is_default_constructible<r>::value << endl; // outputs 0
}


by using std::is_default_constructible, std::is_trivially_default_constructible, std::is_nothrow_default_constructible type traits you can not only check if a type has a default constructor but you can check for the properties of it. like if it's a trivial constructor.


MSDN says that the header defines has_default_constructor and such traits.

http://msdn.microsoft.com/en-us/library/bb982179.aspx


You may want to check this code example taken from libstdc++ in Gcc 4.6.1 and which I slightly modified to work with MSVC 2010 :

/!\ : is_default_constructible returns true even if the default constructor is private or protected, i still can't find a way to solve this, any idea ?) :

namespace std {

namespace detail {

template<typename _B1, typename _B2>
struct __and_
  : public conditional<_B1::value, _B2, _B1>::type
{ };

template<typename _Pp>
struct __not_
  : public integral_constant<bool, !_Pp::value>
{ };


template<typename _Tp>
struct __is_array_known_bounds
  : public integral_constant<bool, (extent<_Tp>::value > 0)>
{ };

template<typename _Tp>
struct __is_array_unknown_bounds
  : public __and_<is_array<_Tp>, __not_<extent<_Tp>>>::type
{ };

struct __do_is_default_constructible_impl
{

  template<typename _Tp>
  static true_type __test(int,decltype(_Tp())* a = 0);

  template<typename>
  static false_type __test(...);
};

template<typename _Tp>
  struct __is_default_constructible_impl
  : public __do_is_default_constructible_impl
  {
    typedef decltype(__test<_Tp>(0)) type;
  };

template<typename _Tp>
  struct __is_default_constructible_atom
  : public __and_<__not_<is_void<_Tp>>,
                  __is_default_constructible_impl<_Tp>>::type
  { };

template<typename _Tp, bool = is_array<_Tp>::value>
  struct __is_default_constructible_safe;

// The following technique is a workaround for a current core language
// restriction, which does not allow for array types to occur in 
// functional casts of the form T().  Complete arrays can be default-
// constructed, if the element type is default-constructible, but 
// arrays with unknown bounds are not.
template<typename _Tp>
  struct __is_default_constructible_safe<_Tp, true>
  : public __and_<__is_array_known_bounds<_Tp>,
          __is_default_constructible_atom<typename
                    remove_all_extents<_Tp>::type>>::type
  { };

template<typename _Tp>
  struct __is_default_constructible_safe<_Tp, false>
  : public __is_default_constructible_atom<_Tp>::type
  { };
} // namespace detail
/// is_default_constructible
template<typename _Tp>
  struct is_default_constructible
: public integral_constant<bool, (detail::__is_default_constructible_safe<
                _Tp>::value)>
{ };

}

use :

class DefaultConstructible
{
public:
  DefaultConstructible() {}
};

class NotDefaultConstructible
{
public:
  NotDefaultConstructible(int i) {}
};

std::is_default_constructible<DefaultConstructible>::value // -> true
std::is_default_constructible<NotDefaultConstructible>::value // -> false


After shedding much blood, sweat, and tears, I finally found a way that works on every compiler I tried:

template<class T = void> struct is_default_constructible;

template<> struct is_default_constructible<void>
{
protected:
    // Put base typedefs here to avoid pollution
    struct twoc { char a, b; };
    template<bool> struct test { typedef char type; };
public:
    static bool const value = false;
};
template<> struct is_default_constructible<>::test<true> { typedef twoc type; };

template<class T> struct is_default_constructible : is_default_constructible<>
{
private:
    template<class U> static typename test<!!sizeof(::new U())>::type sfinae(U*);
    template<class U> static char sfinae(...);
public:
    static bool const value = sizeof(sfinae<T>(0)) > 1;
};
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号