Основные понятия C++20: нормализация ограничений

Это пример из стандарта С++ 20 (ISO/IEC 14882:2020), раздел 13.5.4 ([temp.constr.normal]), пункт 1 (выделено мной):

Нормальная форма идентификатора концепта C<A1, A2, ..., An> является нормальной формой выражения ограничения C после замены A1, A2, ..., An на соответствующие параметры шаблона C в отображения параметров в каждом атомарном ограничении. Если любая такая замена приводит к недопустимому типу или выражению, программа неправильно сформирована; диагностика не требуется.

template<typename T> concept A = T::value || true;
template<typename U> concept B = A<U*>;
template<typename V> concept C = B<V&>;

Нормализация выражения ограничения B действительна и приводит к T::value (с отображением T -> U*) V true (с пустым отображением), несмотря на то, что выражение T::value неправильно сформировано для типа указателя T. Нормализация выражения ограничения C приводит к неправильному формату программы, потому что она формирует недопустимый тип V&* в сопоставлении параметров.

Я понимаю, что C делает программу неправильной (и почему). Однако мне не ясно, приведет ли B к тому, что программа будет неправильно сформирована или нет. В тексте говорится, что нормализация B действительна, но в то же время утверждается, что выражение T::value неправильно сформировано из-за этого типа указателя (что я понимаю). Означает ли это, что действительна только часть нормализации процесса, но сама программа неправильно сформирована на более позднем этапе при проверке T::value? Или программа действительна в любом случае и проверка T::value как-то пропускается/избегается?

Я проверил с помощью Godbolt Compiler Explorer, и GCC, и Clang, похоже, с этим справляются. Тем не менее, поскольку в стандарте сказано, что «диагностика не требуется», это не очень помогает.

«мне не ясно, приведет ли B к тому, что программа будет неправильно сформирована или нет». Это прямо говорит, что она действительна; Что еще тебе нужно?

Nicol Bolas 14.02.2023 02:51

GCC и Clang отличаются в остальных случаях. Однако неясно, может ли static_assert показать, что выражение неправильно сформировано, если это выражение IFNDR. Случай нормализованного int&* в ссылке выше, в строке 9, интересен, так как и Clang, и GCC принимают его как действительный или, по крайней мере, не отвергают.

Amir Kirsh 14.02.2023 04:09
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
2
1 426
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Концепция B действительна, так как вы можете передать указатель на концепцию A. Внутри самой A указатель не может получить доступ к ::value, но это, согласно спецификации, [temp.constr.atomic], не будет рассматриваться как ошибка, а скорее как false, тогда || true в понятии A составит полное выражение true.

Обратите внимание, что если мы передаем int& концепту B, то наш код будет IFNDR, так как B попытается передать A недопустимый тип (int&*).

Концепция C является IFNDR как есть, поскольку она передает ссылку на B, который пытается передать указатель на эту ссылку на A, и снова тип V&* недействителен.


Попытка оценить неправильно сформированное выражение, не требующее диагностики, с использованием static_assert не обязательно поможет ответить на вопрос, является ли выражение допустимым или нет, поскольку компилятору не требуется сбой static_assert на неправильно сформированном- выражение, не требующее диагностики.

Обратите внимание, что каждое нормализованное ограничение состоит из 2 частей:
Атомарное ограничение и связанное сопоставление параметров.


Давайте разделим каждое ограничение на эти две части для ваших трех примеров концепций:

В вашем примере нормализованная форма понятия A будет дизъюнкцией этих двух ограничений:

  • Атомное выражение: X::value
    Отображение параметров: X ↦ T
  • Атомное выражение: true
    Нет сопоставления параметров

Нормализованная форма понятия B будет дизъюнкцией этих двух ограничений:

  • Атомное выражение: X::value
    Отображение параметров: X ↦ U*
  • Атомное выражение: true
    Нет сопоставления параметров

И нормализованная форма понятия C будет дизъюнкцией этих двух ограничений:

  • Атомное выражение: X::value
    Отображение параметров: X ↦ V&*
  • Атомное выражение: true
    Нет сопоставления параметров

Как формируются сопоставления параметров

Формирование сопоставления параметров для атомарного выражения очень просто:

По умолчанию атомарное выражение всегда начинается с сопоставления параметров идентификации (т. е. без изменений типа):

13.5.4 Нормализация ограничений [[temp.constr.normal]]
(1) Нормальная форма выражения E — это ограничение, которое определяется следующим образом:
[...]
(1.5) Нормальной формой любого другого выражения E является атомарное ограничение, выражением которого является E, а сопоставление параметров является отображением идентичности.

и единственный способ получить сопоставление параметров, не являющихся идентификаторами, — это назвать другое понятие в рамках ограничения:

13.5.4 Нормализация ограничений [[temp.constr.normal]]
(1.4) Нормальная форма идентификатора концепта C<A1, A2, ..., An> — это нормальная форма выражения ограничения C после замены A1, A2, ..., An на соответствующие параметры шаблона C в сопоставлениях параметров в каждом атомарном ограничении. [...]

Вот несколько примеров:

template<class T> constexpr bool always_true = true;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ T (identity)
template<class T> concept Base = always_true<T>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ U (identity) 
template<class U> concept Foo = Base<U>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ V::type
template<class V> concept Bar = Base<typename V::type>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ W&&
template<class W> concept Baz = Base<W&&>;

Ваш цитируемый раздел

Что возвращает нас к вашему исходному цитируемому разделу:

13.5.4 Нормализация ограничений [[temp.constr.normal]]
(1.4) Нормальная форма идентификатора концепта C<A1, A2, ..., An> — это нормальная форма выражения ограничения C после замены A1, A2, ..., An на соответствующие параметры шаблона C в сопоставлениях параметров в каждом атомарном ограничении. Если любая такая замена приводит к недопустимому типу или выражению, программа неправильно сформирована; диагностика не требуется.

Обратите внимание, что выделенный оператор применяется только к сопоставлению параметров, а не к самому атомарному выражению.

Вот почему концепция C в вашем примере имеет неправильный формат, NDR - потому что сопоставление параметров для его атомарных выражений формирует недопустимый тип (указатель на ссылку): X ↦ V&*

Обратите внимание, что фактический тип, который заменяется на V, не имеет значения на этапе нормализации; единственное, что имеет значение, это то, что само отображение образует недопустимый тип или выражение.

Вот еще несколько примеров:

template<class T> constexpr bool always_true = true;

// well-formed
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ T (identity)
template<class T> concept Base = always_true<T>;

// well-formed
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ U::type
template<class U> concept Foo = Base<typename U::type>;

// ill-formed, ndr (invalid parameter mapping)
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ V*::type
template<class V> concept Bar = Foo<V*>;

// ill-formed, ndr (invalid parameter mapping)
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ W&*
template<class W> concept Baz = Foo<W&>;

Примерная хронология событий во время компиляции

Чтобы ответить на вопрос, когда ваша программа получает неверный формат ndr, нам нужно установить порядок, в котором происходят события во время компиляции.

  • Когда определяются ограничения связанной декларации ИЛИ оценивается значение концепции, происходит нормализация ограничений. Это дается:

    13.5.4 Нормализация ограничений [[temp.constr.normal]]
    [Note 1] Нормализация ограничений-выражений выполняется при определении связанных ограничений объявления и при оценке значения id-выражения, именующего специализацию концепта.

    Именно здесь ваша программа станет неправильно сформированной, ndr, если сопоставление параметров образует недопустимый тип или выражение.

  • После того, как ограничения были нормализованы, фактический тип будет заменен ограничениями:

    13.5.2.3 Атомарные ограничения [[temp.constr.atomic]]
    (3) Чтобы определить, выполняется ли атомарное ограничение, сопоставление параметров и аргументы шаблона сначала подставляются в его выражение. Если подстановка приводит к недопустимому типу или выражению, ограничение не выполняется.

    Обратите внимание, что на этом этапе допускается формирование недопустимых типов или выражений — если это так, то результатом ограничения будет просто false.


Заключение

Итак, чтобы ответить на ваши вопросы:

  • Означает ли это, что действительна только часть нормализации процесса, но сама программа неправильно сформирована на более позднем этапе при проверке T::value?

    Понятия A и B правильно сформированы.
    Концепция C неправильно сформирована, ndr в процессе нормализации.
    Фактическое атомарное ограничение T::value в этом случае не имеет значения; это может быть просто always_true<T>.

  • Или программа действительна в любом случае и проверка T::value каким-то образом пропускается/избегается?

    Программа действительна до тех пор, пока концепция C никогда не нормализуется.
    т. е. его явное вычисление или использование в качестве ограничения сделало бы вашу программу неправильно сформированной, ndr.

    Пример:

    // evaluates concept C
    //   -> results in normalization of C
    //   -> ill-formed, ndr
    static_assert(C</* something */>); 
    
    template<C T>
    void foo() {}
    
    // constraints of foo will be determined
    //   -> results in normalization of C
    //   -> ill-formed, ndr
    foo</* something */>();  
    

Сопоставление учитывает только параметры шаблона, которые появляются в пределах ограничения, поэтому сопоставление true пусто.

T.C. 14.02.2023 15:35

@Т.С. спасибо, я полностью пропустил эту часть в 13.5.2.3 (1). Я обновил свой пост, теперь он должен быть правильным :)

Turtlefight 14.02.2023 19:12

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