Должны ли типы шаблонов, параметризованные локальными статическими переменными из одной и той же функции шаблона, сравниваться одинаково?

Рассмотрим следующую программу (godbolt):

template <typename, typename>
struct is_same { static constexpr bool value = false; };

template <typename T>
struct is_same<T, T> { static constexpr bool value = true; };

template <typename T, typename U>
static constexpr bool is_same_v = is_same<T, U>::value;

using uintptr_t = unsigned long long;

template <int const* I>
struct Parameterized { int const* member; };

template <typename T>
auto create() {
    static constexpr int const I = 2;

    return Parameterized<&I>{ &I };
}

int main() {
    auto one = create<short>();
    auto two = create<int>();

    if (is_same_v<decltype(one), decltype(two)>) {
        return reinterpret_cast<uintptr_t>(one.member) == reinterpret_cast<uintptr_t>(two.member) ? 1 : 2;
    }

    return 0;
}

На основе n4659 (окончательный рабочий проект С++ 17):

§ 17.4 [тип темп.]/1:

Два идентификатора шаблона ссылаются на один и тот же класс, функцию или переменную, если:

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

Я бы ожидал, что:

  • Либо существует один экземпляр static constexpr int const I = 2; для всех экземпляров create<T>, и в этом случае decltype(one) относится к тому же классу, что и delctype(two).
  • Или есть один экземпляр static constexpr int const I = 2; для каждого экземпляра create<T>, и в этом случае они относятся к другому классу.

Тем не менее, при использовании GCC или Clang (любая версия, которая создает двоичный файл) результатом main является 2, указывающее:

  • Один и тот же класс для one и two.
  • И все же другой экземпляр create<T>()::I.

Листинг сборки подтверждает, что созданы 2 экземпляра: _ZZ6createIsEDavE1I (он же create<short>()::I) и _ZZ6createIiEDavE1I (он же create<int>()::I).

Согласно стандарту C++17, должны ли типы one и two быть одинаковыми или нет?


Интересный вариант: замена = 2 на = sizeof(T) приводит к тому, что типы меняются (см. стрела бога).

Обнаружено при изучении передачи char const* в качестве параметра шаблона, как показано на stackoverflow.com/q/65275694/147192.

Matthieu M. 15.12.2020 14:21

Для любопытных сообщается как gcc.gnu.org/bugzilla/show_bug.cgi?id=98288. Давайте узнаем мнение(я) разработчиков GCC.

Matthieu M. 15.12.2020 14:59
ДВС на багажнике gcc, так или иначе есть баг.
Passer By 15.12.2020 15:35

@PasserBy: Хорошая находка. Почему-то не додумался проверить багажник...

Matthieu M. 15.12.2020 15:39

И также сообщается как bugs.llvm.org/show_bug.cgi?id=48517; с бонусной проблемой компоновщика для clang.

Matthieu M. 15.12.2020 17:45
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
219
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Я считаю, что поведение clang/gcc нарушает стандарт (возможно, неправильная оптимизация «как если бы»?), согласно [intro.object] (выделено мной)

Если объект не является битовым полем или подобъектом нулевого размера, адрес этого объекта является адресом первого байт, который он занимает. Два объекта с перекрывающимися временами жизни, которые не являются битовыми полями, могут иметь один и тот же адрес, если один вложен в другой, или если хотя бы один является подобъектом нулевого размера и они разных типов; в противном случае они имеют разные адреса и занимают непересекающиеся байты памяти.

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

Сноска (ненормативная) здесь также актуальна:

по правилу «как если бы» реализации разрешается хранить два объекта по одному и тому же машинному адресу или не хранить объект вообще если программа не может заметить разницу

Однако в этом случае есть разница в поведении, когда объекты расположены по одному и тому же адресу машины (даже если мы можем гарантировать, что их значение одинаково), поэтому они не должны занимать один и тот же адрес.

Следует отметить, что MSVC всегда возвращает 0 независимо от уровня оптимизации, что я считаю правильным поведением.

Ой! Я не знал, что могу протестировать MSVC с помощью godbolt! Это очень интересная точка данных, и да, я также считаю, что результат должен быть равен 0, хотя я мог бы принять 1, поскольку он, по крайней мере, будет согласовываться между доменами типа и значения.

Matthieu M. 15.12.2020 15:31

Согласно комментарию к моему отчету об ошибке Clang, это было исправлено на github.com/llvm/llvm-project/commit/… (в день отправки отчета!).

Matthieu M. 17.12.2020 10:15

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