为什么使用两个sizeofs来检查一个类是否是默认可构造的,但是一个不是?

时间:2012-09-11 06:41:12

标签: c++ templates metaprogramming

我使用了“Is there a way to test whether a C++ class has a default constructor (other than compiler-provided type traits)?”中的代码。

我稍微修改了它以适用于我的所有测试用例:

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;


    // the second version does not work
#if 1
    template<int x, int y> class is_equal {};
    template<int x> class is_equal<x,x> { typedef void type; };

    template< class U >
    static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );
#else
    template<int x> class is_okay { typedef void type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof U() >::type * );
#endif

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

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

为什么它与两个模板参数版本一起正常工作但不能正常工作(设置#if 0)? 这是编译器错误吗?我正在使用Visual Studio 2010。

我使用了以下测试:

BOOST_STATIC_ASSERT( is_default_constructible<int>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( !is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

我真的很茫然:

  1. 其他版本的两项测试失败:int[100]NotDefaultConstructible。所有测试都成功使用了两个模板参数版本。
  2. Visual Studio 2010不支持std::is_default_constructible。但是,我的问题是为什么两个实现有什么不同,为什么一个有效,另一个没有。

2 个答案:

答案 0 :(得分:3)

这似乎肯定是编译器的工件(bug),因为g ++的行为(和失败)不同。我只能猜测为什么VS表现不同,但有一个猜测似乎合理的是这个类:

template<int x> class is_okay { typedef void type; };
无论模板参数如何,

都具有相同的定义,因此编译器在分析static sfinae( typename is_okay< sizeof U() >::type * );时可能会跳过一个步骤,并且在没有仔细查看is_okay参数的情况下认为它是明确定义的。所以它认为一切都是默认可构建的。

为什么VS和g ++都不被is_okay::type私有打扰,我不知道。好像他们俩都应该这样。

另一方面,g ++将两个版本视为等效。但是,在两者中,它为int[100]提供了不同的错误。关于它是否应该是默认构造的,这个问题值得商榷。你似乎认为不应该这样。 g ++ 47的std::is_default_constructible认为它是!要获得该行为(可能更为标准),您可以将T替换为typename boost::remove_all_extents<T>::type行中的enum

答案 1 :(得分:3)

(我之前的回答非常了解我的回答。)

首先,请注意您有class is_okay { typedef void type; },即typeis_okay的私人成员。这意味着它在课堂外实际上并不可见,因此

template< class U >
static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );

永远不会成功。但是,SFINAE最初并不适用于C ++ 98中的这种情况;直到DR 1170的解决方案才开始检查[开始]作为替换过程的一部分进行检查&#34;。[1]

  

(令人惊讶的是,Paolo Carlini在10天前写了这篇博文,所以你对这个问题的时机是无可挑剔的。在这种情况下,根据Carlini的说法,GCC之前的GCC在SFINAE期间没有进行访问检查。所有这些都解释了为什么你没有看到GCC抱怨type的私密性。你不得不在不到两周前使用树顶GCC ,以便看到正确的行为。)

Clang(树顶)在-std=c++11模式下跟随DR,但在默认的C ++ 03模式下给出了预期的错误(即Clang不遵循C ++中的DR) 03模式)。这有点奇怪,但也许他们这样做是为了向后兼容。

无论如何,你实际上并不希望type首先是私人的。您打算写的是struct is_equalstruct is_okay

通过此更改,Clang会传递您的所有测试用例。 GCC 4.6.1也传递了所有测试用例,除了 int[100]。海湾合作委员会认为int[100]是可以的,而你却声称​​ 没法。

但是您的代码的另一个问题是它没有测试您认为它的测试内容。 C ++标准,第8.5#10条,非常清楚地说:[2]

  

一个对象,其初始化程序是一组空的括号,即(),应进行值初始化。

因此,当您撰写sizeof U()时,如果U 默认 -initialized,则您不会测试;如果它可以 value -initialized,那么你正在测试它。

......或者你呢?至少在我的一些测试用例中,GCC的错误消息表明U()被解释为类型的名称 - &#34;函数返回U&#34; - 而 int[100]表现不同的原因。我不知道这种行为是如何有效的,但我真的不明白这里的句法细微之处。

如果你真的打算测试默认初始化,那么你应该使用sizeof *new U之类的东西,你现在拥有sizeof U()

顺便说一句,int[100] 默认初始化的期间。标准明确了默认初始化数组类型的含义。

最后,我想知道你的代码中是否有一个古怪行为的原因是你试图将一个未经修饰的0(类型为int)传递给一个函数,其中重载包括一个void *和一个...的函数。在这种情况下,我完全可以理解编译器是否选择了错误的编译器。建议您尝试将0传递给int的函数。

总而言之,这里的代码版本在ToT Clang和GCC 4.6.1中都非常适合我(即没有断言失败)。

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;

    template<int x> struct is_okay { typedef int type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof (*new U) >::type );

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

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

#if __has_feature(cxx_static_assert)
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail")
#else
#define dummy2(line) dummy ## line
#define dummy(line) dummy2(line)
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1]
#endif

#include <string>

BOOST_STATIC_ASSERT( !is_default_constructible<int()>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );