当切换到c ++ 17并用标准解决方案替换自定义std::optional
解决方案时,检测到了一个非常奇怪且意外的clang 5行为。出于某种原因,由于对参数类的emplace()
特征的错误评估,std::is_constructible
被禁用。
在复制之前必须满足一些特定的先决条件:
#include <optional>
/// Precondition #1: T must be a nested struct
struct Foo
{
struct Victim
{
/// Precondition #2: T must have an aggregate-initializer
/// for one of its members
std::size_t value{0};
};
/// Precondition #3: std::optional<T> must be instantiated in this scope
std::optional<Victim> victim;
bool foo()
{
std::optional<Victim> foo;
// An error
foo.emplace();
/// Assertion is failed
static_assert(std::is_constructible<Victim>::value);
}
};
上的实例
更改任何前提条件并按预期编译。标准中是否存在一些未知的不一致性,使得clang在符合要求时拒绝此代码?
作为旁注: GCC 7.1 和 GCC 7.2 对上述代码没有任何问题。
错误报告位于:bugs.llvm.org
答案 0 :(得分:7)
这看起来像编译器错误。来自[class]
在类说明符的结束
}
,类被视为完全定义的对象类型(或完整类型)。
这意味着Victim
在std::optional<Victim>
处完成,使其与此上下文中的任何其他类型没有区别。
来自[meta]
当且仅当以下变量定义适用于某些发明变量
is_constructible<T, Args...>
时,才应满足模板特化t
的谓词条件:T t(declval<Args>()...);
使用t
类型的参数直接初始化Args...
,或者如果sizeof...(Args) == 0
,则初始化t
。
在这种情况下,value-initializing t
is to default-initialize t
有效,因此std::is_constructible_v<Victim>
应为真。
尽管如此,编译器似乎正在编译struggling a lot。
答案 1 :(得分:3)
好的,挖出相关的报价。问题的关键是std::is_constructible
应该如何处理Victim
。最具决定性的权威是C ++ 17(n4659)。首先[meta.unary.prop/8]:
模板特化的谓词条件 当且仅当,
is_constructible<T, Args...>
才会得到满足 对于一些发明的人来说,下面的变量定义会很好 变量t:T t(declval<Args>()...);
[注意:这些令牌永远不会被解释为函数声明。 - 结束注释]访问检查就像在上下文中一样执行 与T和任何Args无关。只有立即的有效性 考虑变量初始化的上下文。
我强调的注释不是规范性的(因为是注释),但它与[temp.variadic]/7重合:
...当N为零时,扩展的实例化产生一个 空列表。这样的实例化不会改变句法 对封闭结构的解释,即使在其中 完全省略该列表否则将是不正确的或将会 导致语法含糊不清。
因此,出于is_constructible
的目的,此T t();
确实使t
成为变量声明。这个初始化是值初始化,因为[dcl.init/11]说的很多:
一个对象,其初始化程序是一组空的括号,即(), 应进行价值初始化。
这意味着特征最终会检查Victim
是否可以进行值初始化。它可能。它是一个聚合,但隐式默认的默认c'tor仍然由编译器定义(显然支持值初始化)。
长话短说。 Clang有一个错误,你应该报告它。