Допустимо ли имя типа необязательного <void>?

При изменении существующего кода я случайно обнаружил, что объявление псевдонима создано 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>>);

cppreference говорит:

Здесь нет необязательных ссылок, функций, массивов или cv void; программа является неправильной, если она создает необязательный параметр такого типа.

Итак, независимо от того, что компиляторы не жалуются, является ли этот код плохо сформированным?

std::optional должен содержать значение. Вы не можете создать экземпляр void, поэтому я не понимаю, как std::optional может содержать void.
user4581301 05.07.2024 23:52

@user4581301 user4581301 - это имя типа, объекта нет. Имя типа может участвовать во многих других шаблонах, которые не создают объект.

Gene 05.07.2024 23:54

Итак, вы говорите, что вы дошли до того, что имя определено, но никогда не используется, поэтому экземпляр optional<void> не создается. Зависимость от экземпляров должна быть безопасной. Я добавил тег language-lawyer, потому что считаю, что любой достойный ответ должен будет сильно опираться на интерпретации Стандарта.

user4581301 06.07.2024 00:07

@ user4581301 — Я предполагаю, что экземпляр шаблона должен быть создан, но объект не создан.

Gene 06.07.2024 00:08

Пока создание экземпляра не используется ODR, вы не должны столкнуться с ошибкой, которая обычно эквивалентна static_assert(!std::is_void_v<T>) в теле шаблона класса, которая также охватывает другие запрещенные типы, такие как std::nullopt_t и std::in_place_t.

Patrick Roberts 06.07.2024 00:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
5
176
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Существуют особые обстоятельства, когда шаблоны (классов) создаются неявно: см. [temp.inst], хотя в некоторых случаях это может варьироваться в зависимости от реализации. Ни в коем случае простое упоминание идентификатора шаблона не приводит к созданию экземпляра; обычно это происходит, когда тип должен быть полным.

В C++20 можно объявить шаблон с ограничениями, которые вступают в силу, даже если специализация шаблона просто названа:

template<class T> requires !std::is_void_v<T>
struct foo {…};

До C++20 можно было вызвать ошибку замены с параметрами шаблона по умолчанию, хотя для шаблона класса, такого как std::optional, дополнительные параметры шаблона, даже установленные по умолчанию, были бы несоответствующими.

Patrick Roberts 06.07.2024 16:24

@PatrickRoberts: Верно, но вы могли бы указать аргумент, чтобы избежать использования значения по умолчанию, и, как вы намекаете на другую арность, можно будет наблюдать с помощью параметров шаблона шаблона.

Davis Herring 06.07.2024 18:22
Ответ принят как подходящий

[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 просто не из их числа.

Другие вопросы по теме