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

本小妞迷上赌 提交于 2020-08-22 08:18:47

问题


A really strange and unexpected behaviour of clang 5 was detected when switching to c++17 and replacing custom std::optional solution with the standard one. For some reason, emplace() was being disabled due to faulty evaluation of a std::is_constructible trait of the parameter class.

Some specific preconditions must be satisfied before it reproduces:

#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);
    }
};

Live example on godbolt.org


Change any of the preconditions and it compiles as expected. Is there some unknown inconsistency in the standard that makes clang reject this code while being compliant?

As a side note: GCC 7.1 and GCC 7.2 have no problem with the above code.


Bug report at: bugs.llvm.org


回答1:


This looks like a compiler bug. From [class]

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

Which means Victim is complete at std::optional<Victim>, making it no different than any other type in this context.

From [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>()...);

Which is direct-initializing t with arguments of type Args..., or if sizeof...(Args) == 0, it's value-initializing t.

In this case, value-initializing t is to default-initialize t, which is valid hence std::is_constructible_v<Victim> should be true.

With all that said, compilers seems to be struggling a lot compiling this.




回答2:


Alright, dug up the relevant quotes. The crux of the matter is how std::is_constructible should handle Victim. The most conclusive authority is C++17 (n4659). First [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.

The note I highlighted is not normative (on account of being a note), but it coincides with [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.

So for the purposes of is_­constructible, this T t(); indeed makes t a variable declaration. This initialization is value initialization because [dcl.init/11] says as much:

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

That means that the trait ends up checking if Victim can be value-initialized. Which it may. It's an aggregate, but an implicitly defaulted default c'tor is still defined by the compiler (to support value initialization, obviously).

Long story short. Clang has a bug, you should report it.



来源:https://stackoverflow.com/questions/47974898/clang-5-stdoptional-instantiation-screws-stdis-constructible-trait-of-the-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!