Clang 5: std:: дополнительные экземпляры для создания экземпляров std:: is_constructible признак типа параметра
Было обнаружено действительно странное и неожиданное поведение clang 5 при переходе на С++ 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);
}
};
Пример Live на godbolt.org
Измените любое из предварительных условий и он скомпилируется, как и ожидалось. Есть ли какая-то неизвестная несогласованность в стандарте, которая заставляет clang отклонять этот код, будучи совместимым?
В качестве побочного примечания: GCC 7.1 и GCC 7.2 не имеют проблем с указанным выше кодом.
Отчет об ошибках: bugs.llvm.org
Ответы
Ответ 1
Это похоже на ошибку компилятора. Из [class]
Класс считается полностью определенным типом объекта (или полным типом) при закрытии }
спецификатора класса.
Это означает, что Victim
завершен в std::optional<Victim>
, что делает его не иначе, как любой другой тип в этом контексте.
Из [meta]
Условие предиката для специализированной специализации is_constructible<T, Args...>
должно выполняться тогда и только тогда, когда следующее определение переменной будет хорошо сформировано для некоторой изобретенной переменной t
: T t(declval<Args>()...);
Что такое прямая инициализация t
с аргументами типа Args...
, или если sizeof...(Args) == 0
, это инициализация значения t
.
В этом случае инициализация значения t
- это значение по умолчанию-initialize t
, которое верно, поэтому std::is_constructible_v<Victim>
должно быть правда.
Со всем сказанным компиляторы, похоже, много борются, компилируя это.
Ответ 2
Хорошо, выкопали соответствующие цитаты. Суть в том, как std::is_constructible
должен обрабатывать Victim
. Наиболее убедительным является С++ 17 (n4659). Первый [meta.unary.prop/8]:
Условие предиката для типовой специализации is_constructible<T, Args...>
должно выполняться тогда и только тогда, когда следующее определение переменной будет хорошо сформировано для некоторых изобретенных переменная t:
T t(declval<Args>()...);
[Примечание: Эти токены никогда не интерпретируются как объявление функции. - end note] Проверка доступа выполняется как в контексте не связанных с T и любого из Args. Только действительность рассматривается контекст инициализации переменных.
Заметка, которую я выделил, не является нормативной (из-за записи), но она совпадает с [temp.variadic]/7
... Когда N равно нулю, экземпляр разложения дает пустой список. Такое создание не изменяет синтаксический интерпретация вложенной конструкции, даже в тех случаях, когда в противном случае исключение списка в противном случае было бы плохо сформированным или приводит к двусмысленности в грамматике.
Итак, для is_constructible
этот T t();
действительно делает t
объявление переменной. Эта инициализация инициализируется значением, потому что [dcl.init/11] говорит так:
Объект, инициализатор которого представляет собой пустой набор скобок, т.е.(), должен инициализироваться значением.
Это означает, что этот признак заканчивается проверкой того, может ли Victim
инициализироваться значение. Что это может быть. Это совокупность, но неявно дефолтный по умолчанию c'tor по-прежнему определяется компилятором (для поддержки инициализации значения, очевидно).
Короче говоря. У Clang есть ошибка, вы должны сообщить об этом.