clang 5: std:: необязательные винты реализации std::is_constructible черта типа параметра
Действительно странное и неожиданное поведение clang 5 было обнаружено при переходе на C++17 и замене пользовательских std::optional
решение со стандартным. По какой-то причине 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.1 и GCC 7.2 нет проблем с приведенным выше кодом.
Сообщение об ошибке по адресу: bugs.llvm.org
2 ответа
Это похоже на ошибку компилятора. Из [класса]
Класс считается полностью определенным типом объекта (или завершенным типом) при закрытии
}
спецификатора класса.
Что значит Victim
завершено в std::optional<Victim>
, делая его ничем не отличающимся от любого другого типа в этом контексте.
Из [мета]
Условие предиката для специализации шаблона
is_constructible<T, Args...>
должны быть выполнены тогда и только тогда, когда следующее определение переменной будет правильно сформировано для некоторой изобретенной переменнойt
:T t(declval<Args>()...);
Который является прямой инициализацией t
с аргументами типа Args...
, или если sizeof...(Args) == 0
это инициализация значения t
,
В этом случае инициализация значения t
по умолчанию инициализировать t
, что справедливо отсюдаstd::is_constructible_v<Victim>
должно быть правдой.
С учетом всего вышесказанного, компиляторы, похоже, изо всех сил пытаются компилировать это.
Хорошо, выкопал соответствующие цитаты. Суть вопроса в том, как 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'or по умолчанию все еще определяется компилятором (очевидно, для поддержки инициализации значения).
Короче. У Clang есть ошибка, вы должны сообщить об этом.