【问题标题】:Why does using two sizeofs work to check whether a class is default constructible, but one does not?为什么使用两个 sizeof 来检查一个类是否是默认可构造的,而一个却不行?
【发布时间】:2012-09-04 01:13:05
【问题描述】:

我使用了来自“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。但是,我的问题是为什么这两种实现有任何差异,以及为什么一种有效而另一种无效。

【问题讨论】:

  • 为什么不检查您是否已经在标准库中以std::is_default_constructible 的形式拥有它?
  • 什么不起作用?它在 g++ 上运行良好,除了BOOST_STATIC_ASSERT( !is_default_constructible&lt;int[100]&gt;::value );
  • @BЈовић 如果断言失败,并且该断言是正确的,那么它肯定不能正常工作吗?
  • g++-4.7.1 可以正确编译这两个变体。
  • 你应该使用typedef char yes[1]; typedef char no[2];来保证它们是不同的尺寸(intchar理论上可以是相同的尺寸);另外我觉得它更容易阅读。

标签: c++ templates metaprogramming


【解决方案1】:

(我的回答很大程度上来自 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 (top-of-tree) 在 -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 是否可以默认初始化;您正在测试它是否可以被 value 初始化!

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

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

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

最后,我想知道你的代码中古怪行为的一个原因是否是你试图将一个朴素的0(类型为int)传递给一个函数,该函数的一组重载包括一个函数采用@ 987654345@ 和一个采取...。在这种情况下,如果编译器选择了错误的编译器,我完全可以理解。建议您尝试将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 );

【讨论】:

  • 您已经很好地解释了这一点。我的目标是让它失败int[100],因为我编写了一个带有一个可选参数T defaultValue = T() 的模板函数,这在VS2010 中不适用于T=int[100]。直到现在我才知道默认初始化和值初始化。
【解决方案2】:

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

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

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

我不知道为什么 VS 和 g++ 都不会因为 is_okay::type 是私有的而烦恼。似乎他们都应该是。

另一方面,

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2021-02-20
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多