Действительно странное и неожиданное поведение 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
Это похоже на ошибку компилятора. От [учебный класс]
Класс считается полностью определенным типом объекта (или завершенным типом) при закрытии
}
спецификатора класса.
Что значит 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(declval<Args>()...);
- конец примечания] Проверка доступа выполняется как в контексте
не связанный с T и любым из Args. Только действительность немедленного
контекст инициализации переменной рассматривается.
Заметка, которую я выделил, не является нормативной (из-за того, что она является заметкой), но совпадает с [Temp.variadic] / 7:
… Когда N равно нулю, создание экземпляра расширения производит
пустой список Такое создание не изменяет синтаксический
интерпретация ограждающей конструкции, даже в тех случаях, когда
полное исключение списка в противном случае было бы неверным или
привести к двусмысленности в грамматике.
Так что для целей is_constructible
, этот T t();
действительно делает t
объявление переменной. Эта инициализация является инициализацией значения, потому что [Dcl.init / 11] говорит так же:
Объект, инициализатором которого является пустой набор скобок, т.е. (),
должен быть инициализирован значением.
Это означает, что черта в конечном итоге проверяет, Victim
может быть инициализирован значением. Что это может. Это агрегат, но неявно заданный по умолчанию c’or по умолчанию все еще определяется компилятором (очевидно, для поддержки инициализации значения).
Короче. У Clang есть ошибка, вы должны сообщить об этом.