Reputation: 299810
Consider the following program (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;
}
Based on n4659 (C++17 final working draft):
§ 17.4 [temp.type]/1:
Two template-ids refer to the same class, function, or variable if:
- their template-names, operator-function-ids, or literal-operator-ids refer to the same template and
- their corresponding type template-arguments are the same type and
- their corresponding non-type template arguments of integral or enumeration type have identical values and
- their corresponding non-type template-arguments of pointer type refer to the same object or function or are both the null pointer value and
- their corresponding non-type template-arguments of pointer-to-member type refer to the same class member or are both the null member pointer value and
- their corresponding non-type template-arguments of reference type refer to the same object or function and
- their corresponding template template-arguments refer to the same template.
I would expect that:
static constexpr int const I = 2;
for all instantiations of create<T>
, in which case decltype(one)
refers to the same class as delctype(two
).static constexpr int const I = 2;
for each instantiation of create<T>
, in which case the two refer to a different class.Yet, when using GCC or Clang (any version which produces a binary), the result of main is 2
indicating:
one
and two
.create<T>()::I
.The assembly listing confirms that 2 instances are created: _ZZ6createIsEDavE1I
(aka create<short>()::I
) and _ZZ6createIiEDavE1I
(aka create<int>()::I
).
According to the C++17 standard, should the types of one
and two
be the same, or not?
Interesting variation: replacing = 2
by = sizeof(T)
results in the types being different (see godbolt).
Upvotes: 3
Views: 247
Reputation: 41100
I believe the behavior of clang/gcc is in violation of the standard (possibly an improper as-if optimization?), per [intro.object] (emphasis mine)
Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage.
Since the objects are not nested within each other, nor is either a subobject of zero size, they are not allowed to have the same address.
The (non-normative) footnote here is also relevant:
under the “as-if” rule an implementation is allowed to store two objects at the same machine address or not store an object at all if the program cannot observe the difference
However in this case, there is a behavior difference when the objects are located at the same machine address (even if we can guarantee that their value is the same), so they should not occupy the same address.
It should be noted that MSVC always returns 0 regardless of optimization level, which I think is the correct behavior.
Upvotes: 2