clang 5: std::optional 实例化螺丝 std::is_constructible 参数类型的特征

clang 5: std::optional instantiation screws std::is_constructible trait of the parameter type

切换到 c++17 并将自定义 std::optional 解决方案替换为标准解决方案时,检测到 clang 5 的一个非常奇怪和意外的行为。出于某种原因,emplace() 由于对参数 class.

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

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

A class is considered a completely-defined object type (or complete type) at the closing } of the class-specifier.

这意味着 Victimstd::optional<Victim> 处完成,使其与此上下文中的任何其他类型没有区别。

来自[meta]

The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable 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 编译它。

好了,把相关的语录都挖出来了。问题的关键是std::is_constructible应该如何处理Victim。最权威的是C++17 (n4659)。首先 [meta.unary.prop/8]:

The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(declval<Args>()...);

[ Note: These tokens are never interpreted as a function declaration.  — end note ] Access checking is performed as if in a context unrelated to T and any of the Args. Only the validity of the immediate context of the variable initialization is considered.

我强调的注释不规范(因为是注释),但与[temp.variadic]/7:

一致

... When N is zero, the instantiation of the expansion produces an empty list. Such an instantiation does not alter the syntactic interpretation of the enclosing construct, even in cases where omitting the list entirely would otherwise be ill-formed or would result in an ambiguity in the grammar.

所以为了 is_­constructible 的目的,这个 T t(); 确实使 t 成为一个变量声明。此初始化是值初始化,因为 [dcl.init/11] 表示:

An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

这意味着特征最终会检查 Victim 是否可以进行值初始化。它可能。它是一个聚合,但隐式默认的默认 c'tor 仍然由编译器定义(显然是为了支持值初始化)。

长话短说。 Clang 有一个错误,你应该报告它。