为什么使用两个sizeofs可以检查一个类是否默认可构造,而使用一个却不行

Why does using two sizeofs work to check whether a class is default constructible, but one does not?

本文关键字:一个 默认 检查 两个 sizeofs 为什么 是否      更新时间:2023-10-16

我使用了"有没有办法测试C++类是否有默认构造函数(而不是编译器提供的类型特征)?"中的代码。

我对它进行了轻微的修改,以使用我的所有测试用例:

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。然而,我的问题是,为什么这两种实现有任何区别,为什么一种有效,另一种无效

(DS之前的回答大大影响了我的回答。)

首先,请注意您有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的决议通过,"访问检查才[开始]作为替换过程的一部分"。[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_equalstruct 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 U()的所有地方使用类似sizeof *new 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 );

这几乎可以肯定是编译器的一个工件(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认为它是!要获得这种行为(可能更标准),可以在enum行中将T替换为typename boost::remove_all_extents<T>::type

相关文章: