При изменении существующего кода я случайно обнаружил, что объявление псевдонима создано std::optional<void>
. Это произошло из кода шаблона следующим образом:
using ret_t = std::invoke_result_t<Fn, Args...>;
using opt_ret_t = std::optional<ret_t>;
Позже код различает случай, когда тип возвращаемого значения равен (не) void
, поэтому на самом деле объект optional
никогда не создается.
Чтобы убедиться, что я ничего не упустил, я добавил утверждение в эту ветку кода, и все скомпилировалось:
static_assert(std::is_same_v<opt_ret_t, std::optional<void>>);
Здесь нет необязательных ссылок, функций, массивов или cv void; программа является неправильной, если она создает необязательный параметр такого типа.
Итак, независимо от того, что компиляторы не жалуются, является ли этот код плохо сформированным?
@user4581301 user4581301 - это имя типа, объекта нет. Имя типа может участвовать во многих других шаблонах, которые не создают объект.
Итак, вы говорите, что вы дошли до того, что имя определено, но никогда не используется, поэтому экземпляр optional<void>
не создается. Зависимость от экземпляров должна быть безопасной. Я добавил тег language-lawyer
, потому что считаю, что любой достойный ответ должен будет сильно опираться на интерпретации Стандарта.
@ user4581301 — Я предполагаю, что экземпляр шаблона должен быть создан, но объект не создан.
Пока создание экземпляра не используется ODR, вы не должны столкнуться с ошибкой, которая обычно эквивалентна static_assert(!std::is_void_v<T>)
в теле шаблона класса, которая также охватывает другие запрещенные типы, такие как std::nullopt_t
и std::in_place_t
.
Существуют особые обстоятельства, когда шаблоны (классов) создаются неявно: см. [temp.inst], хотя в некоторых случаях это может варьироваться в зависимости от реализации. Ни в коем случае простое упоминание идентификатора шаблона не приводит к созданию экземпляра; обычно это происходит, когда тип должен быть полным.
В C++20 можно объявить шаблон с ограничениями, которые вступают в силу, даже если специализация шаблона просто названа:
template<class T> requires !std::is_void_v<T>
struct foo {…};
До C++20 можно было вызвать ошибку замены с параметрами шаблона по умолчанию, хотя для шаблона класса, такого как std::optional
, дополнительные параметры шаблона, даже установленные по умолчанию, были бы несоответствующими.
@PatrickRoberts: Верно, но вы могли бы указать аргумент, чтобы избежать использования значения по умолчанию, и, как вы намекаете на другую арность, можно будет наблюдать с помощью параметров шаблона шаблона.
[res.on.functions]/2.5 утверждает, что поведение не определено, если вы используете неполный тип в качестве аргумента шаблона «при создании экземпляра компонента шаблона или оценке концепции, если это специально не разрешено для этого компонента» в стандартной библиотеке. .
void
— неполный тип ([basic.types.general]/5), а std::optional
не является одним из шаблонов стандартной библиотеки, допускающих неполные типы ([optional.optional.general]/3). Итак, если вы создадите экземпляр std::optional<void>
, программа может не скомпилироваться или она может скомпилироваться и вести себя непредсказуемо во время выполнения (но на практике первое, что происходит).
Однако сделать что-то вроде этого
using T = std::optional<void>;
не создает экземпляр std::optional<void>
. Согласно [temp.inst]/2 и [temp.inst]/11, специализация шаблона класса неявно создается только тогда, когда она используется способом, требующим завершения специализации, или когда полнота специализация влияет на семантику программы. Когда вы просто упоминаете имя специализации шаблона класса или объявляете для него псевдоним, специализация шаблона класса не обязательно должна быть полной, и нет никакой разницы между объявлением псевдонима для полного или неполного типа. Таким образом, в этом случае мы не получаем неявного создания экземпляра.
В стандартной библиотеке нет соответствующего правила для использования специализаций шаблона класса без создания экземпляров. Действительно, если U
— неполный тип класса, полезно иметь возможность сделать что-то вроде этого:
class U;
std::optional<U> getU();
class U { /* ... */ };
// U is now complete
std::optional<U> getU() {
// define the function
}
Использование типа в качестве возвращаемого типа не требует его завершения до тех пор, пока функция не будет фактически определена, поэтому разрешено использовать std::optional<U>
в качестве возвращаемого типа в неопределяющем объявлении до завершения U
.
Если бы существовало какое-либо правило, специально запрещающее использование std::optional<void>
без создания экземпляров, его нужно было бы найти в описании самого std::optional
. Поскольку std::optional
определяется как
namespace std {
template <class T>
class optional {
// ...
};
// (deduction guide omitted)
}
ничего плохого не произойдет, если вы просто передадите void
в параметр шаблона T
без создания экземпляра шаблона класса, но, как указывает Дэвис Херринг, могут быть некоторые шаблоны классов, в которых простое упоминание названия специализации будет недействительным даже без создания экземпляра. std::optional
просто не из их числа.
std::optional
должен содержать значение. Вы не можете создать экземплярvoid
, поэтому я не понимаю, какstd::optional
может содержатьvoid
.