Рассмотрим следующую программу (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)
приводит к тому, что типы меняются (см. стрела бога).
Для любопытных сообщается как gcc.gnu.org/bugzilla/show_bug.cgi?id=98288. Давайте узнаем мнение(я) разработчиков GCC.
@PasserBy: Хорошая находка. Почему-то не додумался проверить багажник...
И также сообщается как bugs.llvm.org/show_bug.cgi?id=48517; с бонусной проблемой компоновщика для clang.
Я считаю, что поведение clang/gcc нарушает стандарт (возможно, неправильная оптимизация «как если бы»?), согласно [intro.object] (выделено мной)
Если объект не является битовым полем или подобъектом нулевого размера, адрес этого объекта является адресом первого байт, который он занимает. Два объекта с перекрывающимися временами жизни, которые не являются битовыми полями, могут иметь один и тот же адрес, если один вложен в другой, или если хотя бы один является подобъектом нулевого размера и они разных типов; в противном случае они имеют разные адреса и занимают непересекающиеся байты памяти.
Поскольку объекты не вложены друг в друга и не являются подобъектами нулевого размера, они не могут иметь одинаковый адрес.
Сноска (ненормативная) здесь также актуальна:
по правилу «как если бы» реализации разрешается хранить два объекта по одному и тому же машинному адресу или не хранить объект вообще если программа не может заметить разницу
Однако в этом случае есть разница в поведении, когда объекты расположены по одному и тому же адресу машины (даже если мы можем гарантировать, что их значение одинаково), поэтому они не должны занимать один и тот же адрес.
Следует отметить, что MSVC всегда возвращает 0 независимо от уровня оптимизации, что я считаю правильным поведением.
Ой! Я не знал, что могу протестировать MSVC с помощью godbolt! Это очень интересная точка данных, и да, я также считаю, что результат должен быть равен 0, хотя я мог бы принять 1, поскольку он, по крайней мере, будет согласовываться между доменами типа и значения.
Согласно комментарию к моему отчету об ошибке Clang, это было исправлено на github.com/llvm/llvm-project/commit/… (в день отправки отчета!).
Обнаружено при изучении передачи
char const*
в качестве параметра шаблона, как показано на stackoverflow.com/q/65275694/147192.