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

Posted

技术标签:

【中文标题】clang 5:std::optional 实例化参数类型的 std::is_constructible 特征【英文标题】:clang 5: std::optional instantiation screws std::is_constructible trait of the parameter type 【发布时间】:2018-06-07 01:53:08 【问题描述】:

当切换到 c++17 并用标准解决方案替换自定义 std::optional 解决方案时,检测到 clang 5 的一个非常奇怪和意外的行为。出于某种原因,由于对参数类的 std::is_constructible 特征的错误评估,emplace() 被禁用。

在复制之前必须满足一些特定的先决条件:

#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 value0;
    ;

    /// 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

【问题讨论】:

很可能是编译器错误。 @CrisLuengo,我希望如此,因为它比标准更容易修复。 实际上,您的问题是语言律师问题。应该这样回答。 检查this。这是所涉及特征的简单实现,在这种情况下应该都是正确的。 gcc 7.2 有最详细的错误消息 “在解析 'Foo::Victim::value' 的非静态数据成员之前需要构造函数” 似乎任何默认初始化程序与可选成员都嵌套在结构中的组合打破了这一点。 ` = 0` 而不是 value 上的 0 也会失败。 【参考方案1】:

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

在类说明符的结尾 处,类被视为完全定义的对象类型(或完整类型)。

这意味着Victimstd::optional&lt;Victim&gt; 处是完整的,这使得它与此上下文中的任何其他类型没有什么不同。

来自[meta]

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

使用Args... 类型的参数直接初始化t,或者如果sizeof...(Args) == 0,它是值初始化t

在这种情况下,value-initializing t is to default-initializet 是有效的,因此 std::is_constructible_v&lt;Victim&gt; 应该为真。

话虽如此,编译器似乎正在 struggling a lot 编译它。

【讨论】:

来自同一段:“在类成员规范中,类在 [...] 默认成员初始化器中被视为完整的(包括嵌套类中的此类内容)。”这似乎暗示编译器在看到关闭的 之前无法处理默认成员初始化程序(对于嵌套类,直到他们看到封闭类的关闭)。 @cpplearner 我认为这是指(封闭)类在其自身中不完整,而不是其嵌套类不完整 我的意思是,默认成员初始化器仍然是封闭类中的令牌汤(即它们不被解析),并且初始化聚合将需要引用这些默认成员初始化器。这在 SFINAE 上下文之外更为明显:struct Outer struct Inner int x = 4; ; decltype(Inner()) a; ; @cpplearner 哦。这确实非常糟糕。虽然我找不到明确使其非法的原因,但它绝对看起来应该是【参考方案2】:

好吧,挖出相关的报价。问题的关键在于std::is_constructible 应该如何处理Victim。最具决定性的权威是 C++17 (n4659)。先[meta.unary.prop/8]:

模板特化的谓词条件 当且仅当满足is_­constructible&lt;T, Args...&gt; 对于某些发明,以下变量定义将是格式良好的 变量 t:

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

[ 注意:这些标记永远不会被解释为函数声明。 — 尾注 ] 访问检查就像在上下文中一样执行 与 T 和任何 Args 无关。只有即时有效 考虑变量初始化的上下文。

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

... 当 N 为零时,展开的实例化产生一个 空列表。这样的实例化不会改变句法 对封闭结构的解释,即使在以下情况下 完全省略该列表将是格式错误的或将 导致语法歧义。

所以对于is_­constructible 而言,这个T t(); 确实使t 成为一个变量声明。这个初始化是值初始化,因为[dcl.init/11] 说了这么多:

一个对象,其初始值设定项是一组空括号,即 (), 应该是值初始化的。

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

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

【讨论】:

以上是关于clang 5:std::optional 实例化参数类型的 std::is_constructible 特征的主要内容,如果未能解决你的问题,请参考以下文章

带有 Clang 10 显式模板实例化的 ~queue 的未定义引用

clang:使用O3导出隐式实例化函数的符号

没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译

std::optional

将一个 std::optional 转换为另一个 std::optional

std::optional - 用 或 std::nullopt 构造空?