18

当切换到 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);
    }
};

godbolt.org上的实时示例


更改任何先决条件并按预期编译。标准中是否存在一些未知的不一致,使 clang 在合规的同时拒绝此代码?

附带说明:GCC 7.1GCC 7.2对上述代码没有任何问题。


错误报告:bugs.llvm.org

4

2 回答 2

7

这看起来像一个编译器错误。来自[班级]

在类说明符结束}时,类被认为是完全定义的对象类型(或完整类型)。

这意味着Victim在 处是完整的std::optional<Victim>,使其与此上下文中的任何其他类型没有什么不同。

[元]

is_­constructible<T, Args...>当且仅当以下变量定义对于某个发明的变量是格式良好的时,模板特化的谓词条件才会被满足tT t(declval<Args>()...);

t使用 type 的参数直接初始化Args...,或者 if sizeof...(Args) == 0,它是 value-initializing t

在这种情况下,value-initializingt是 default-initialize t,这是有效的,因此std::is_constructible_v<Victim>应该是 true。

尽管如此,编译器似乎在编译它时遇到了很多困难

于 2017-12-26T09:05:55.213 回答
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 有一个错误,你应该报告它。

于 2017-12-26T09:05:58.970 回答