19

我使用了“ Is there a way to test a C++ class是否具有默认构造函数(编译器提供的类型特征除外)? ”中的代码。

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

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

为什么它可以与两个模板参数版本一起正常工作,但不能与普通版本( set #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. 但是,我的问题是关于为什么这两种实现有任何差异以及为什么一种有效而另一种无效。
4

2 回答 2

3

(DS 之前的回答对我的回答有很大影响。)

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

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

永远不会成功。但是,SFINAE 在 C++98 中最初并不适用于这种情况;直到 DR 1170 的决议“访问检查 [开始] 作为替换过程的一部分完成”。[1]

(令人惊讶的是,Paolo Carlini 仅在 10 天前写了那篇博客文章,所以你提出这个问题的时机是无可挑剔的。在这种情况下,根据 Carlini 的说法,4.8 之前的 GCC 在 SFINAE 期间根本没有进行访问检查。所以这就解释了为什么您没有看到 GCC 抱怨 . 的私有性type。您必须使用不到两周前的树顶 GCC,才能看到正确的行为。)

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

无论如何,你实际上并不想type一开始就保持私密。你要写的是struct is_equaland struct is_okay

通过此更改,Clang 将通过您的所有测试用例。GCC 4.6.1 也通过了所有的测试用例,除了. int[100]GCC 认为这int[100]问题,而您却断言这不好。

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

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

因此,当您编写 时sizeof U(),您不是在测试是否U可以默认初始化;您正在测试它是否可以进行初始化!

...还是你?至少在我的一些测试用例中,GCC 的错误消息表明它U()被解释为一种类型的名称——“函数返回U”——就是int[100]行为不同的原因。我看不出这种行为是如何有效的,但我真的不明白这里的语法细节。

如果你真的想测试默认初始化,你应该使用sizeof *new U你目前拥有的任何地方sizeof U()

顺便说一句,int[100] 默认初始化的,句号。该标准清楚地说明了默认初始化数组类型的含义。

最后,我想知道代码中古怪行为的一个原因是否是您试图将一个未修饰0的(类型为int)传递给一个函数,该函数的重载集包括一个函数获取void *和一个获取...。在这种情况下,如果编译器选择了错误的编译器,我完全可以理解。最好建议您尝试传递0给函数 take 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 );
于 2012-09-27T20:30:35.320 回答
3

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

template<int x> class is_okay { typedef void type; };

无论模板参数如何,都具有相同的定义,因此编译器在分析时可能会跳过一步static sfinae( typename is_okay< sizeof U() >::type * );并认为它定义良好,而没有仔细查看is_okay. 所以它认为一切都是默认可构造的。

is_okay::type我不知道为什么 VS 和 g++ 都不会被私有化所困扰。似乎他们俩都应该是。

另一方面,g++ 将两个版本视为等效。然而,在这两种情况下,它都会给出不同的错误int[100]。关于它是否应该是默认构造的,这是有争议的。你似乎认为不应该。g++47std::is_default_constructible认为是!要获得该行为(这可能更标准),您可以在行中T替换typename boost::remove_all_extents<T>::typeenum

于 2012-09-27T07:40:02.383 回答