这不是is_default_constructible
. _ 该类型特征只需要检查默认构造的直接上下文,它不必深入评估任何成员初始化程序。这个限制可能是因为它可以在没有专门的编译器魔法的情况下通过使用 SFINAE 来实现。(参见 [meta.unary.prop],尤其是 p7)。
如果元素类型不能默认构造tuple
,则默认构造函数不需要在直接上下文中失败(SFINAE 友好)。LWG 2367pair
已经解决了这个问题,它为默认构造函数引入了以下 SFINAE 要求:tuple
备注:此构造函数不应参与重载决议,除非is_default_constructible<Ti>::value
对 all 为真i
。[...]
有了这个额外的要求,元组的默认构造必须以 SFINAE 友好的方式失败,这样is_default_constructible
现在可以tuple
在元素无法在直接上下文中默认构造的情况下工作(引用类型就是这种情况)。
LWG 2367 目前处于就绪状态;拟议的决议尚未(尚未)纳入 github 草案。
[--这部分还在考虑中
Yakk在评论中提出了一个重要的观点:为什么is_default_constructible
必须深度实例化成员初始化器?
据我所知,这与默认构造函数的条件性constexpr
“活跃性”有关。导致默认构造函数的实例化。它只需要实例化声明以确定是否可以在直接上下文中调用此构造函数而不会失败。但是,声明的实例化需要确定性,这会导致构造函数定义的实例化。tuple
is_default_constructible
constexpr
已被标记为的类模板的成员函数(或构造函数)constexpr
仅是有条件constexpr
的:只有那些类模板实例化的成员函数才会是constexpr
主体不违反constexpr
限制的地方。这需要对构造函数的主体进行实例化,以便构造函数检查constexpr
函数内部是否允许成员初始化器。考虑:
struct nonconstexpr { nonconstexpr() { std::cout << "runtime\n"; } };
struct isconstexpr { constexpr isconstexpr() {} };
template<typename T>
struct wrapper { T t; constexpr wrapper() : t() {} };
在实例化 的默认 ctor 时wrapper
,编译器必须实例化成员初始化器以确定该实例化是否应为constexpr
。
在 的情况下std::tuple
,这会在某处导致成员初始化程序的实例化,该成员初始化程序尝试对引用元组叶(数据成员)进行值初始化。这是一个错误,它不会在默认构造函数的原始实例化的直接上下文中发生。因此,这是一个硬错误,而不是直接上下文中的替换失败。
--]
这部分对我来说并不完全清楚,因为CWG 1358基本上进行了所有实例化constexpr
,无论它们是否真正满足标准。事实上,gcc 6.0 不会编译以下示例,而 gcc 5.1 和 clang 3.7 拒绝它:
#include <type_traits>
template<typename T>
struct foo
{
T t;
constexpr foo() {} // remove `constexpr` to make it compile everywhere
};
int main()
{
static_assert(std::is_default_constructible<foo<int&>>{}, "!");
}
CWG 1358 还告诉我们为什么这两种方法之间的区别 - 有条件的 constexpr 和 constexpr 尽管违规 - 很重要:
在 issue 1581 的讨论中出现了问题,即这种方法——使 constexpr 函数模板或类模板的成员函数的特化仍然是 constexpr 但无法在常量上下文中调用——是否正确。这意味着类类型可能被归类为文字,但不能在编译时实例化。因此,该问题返回“审查”状态,以便进一步考虑该问题。
对于 libc++,存在 bug #21157,已于 2014-10-15 解决,并出现在 clang3.6 分支中。对于 libstdc++,似乎没有错误报告;该问题已在 2015 年 6 月 30 日的合并提交中得到修复,该提交还实现了 N4387 - 改进对和元组(修订版 3),目前似乎没有出现在任何 gcc5 分支中。