为什么使用两个sizeofs可以检查一个类是否默认可构造,而使用一个却不行
Why does using two sizeofs work to check whether a class is default constructible, but one does not?
我使用了"有没有办法测试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 );
我真的不知所措:
- 另一个版本有两个测试失败:
int[100]
和NotDefaultConstructible
。使用双模板参数版本,所有测试都成功 - Visual Studio 2010不支持
std::is_default_constructible
。然而,我的问题是,为什么这两种实现有任何区别,为什么一种有效,另一种无效
(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_equal
和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 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
。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 创建一个包含 c++ 默认值的环境文件
- 在 C++ 中声明 const 对象需要用户定义的默认构造函数.如果我有一个可变成员变量,为什么不呢?
- 如何处理没有默认构造函数但在另一个构造函数中构造的对象?
- 如何在C++中提供模板化函数作为另一个函数的参数,默认值?
- 使用具有默认参数的函数模板进行 decltype 会使结果混乱(一个有趣的问题或 gcc 的错误)
- 如何设置默认参数以防用户不输入另一个参数
- 对于自定义类的一个未定义集,是否有一个默认的散列函数
- 一个C++默认参数可以用另一个参数初始化吗
- C++ enable_if可以有一个默认的实现
- 为什么我们需要一个默认构造函数来通过引用传递对象C++
- 具有一个默认参数的构造函数
- C++是一个类的默认构造函数,调用另一类的另一个默认构造函数
- c++是否有一个默认的数据类,用于以合理的速度对基于索引的访问进行排序
- 为什么自定义数据的 thrust::sort 需要一个默认的构造器,而 STL::sort 不需要?
- 函数模板的非最后一个默认模板参数
- Netbeans在c++中创建了一个默认构造函数.它做什么
- 如果我在默认构造函数中放了一个参数,但给了这个参数一个默认值,它还是一个默认构造函数吗
- 为什么在c++中给typename模板参数一个默认值0 ?