This is a slightly esoteric question, but I was curious whether the following class extension pattern is legal (as in, does not constitute UB) in modern C++ (for all intends and purposes I am fine with restricting the discussion to C++17 and later).
template<typename T>
struct AddOne {
T add_one() const {
T const& tref = *reinterpret_cast<T const*>(this);
return tref + 1;
}
};
template<template<typename> typename E, typename T>
E<T> const& as(T const& obj) {
return reinterpret_cast<E<T> const&>(obj);
}
auto test(float x) {
return as<AddOne>(x).add_one();
}
auto test1(int x) {
return as<AddOne>(x).add_one();
}
// a main() to make this an MVCE
// will return with the exit code 16
int main(int argc, const char * argv[]) {
return test1(15);
}
The above code is a complete example, it compiles, runs and produces the expected result with at least clang in C++17 mode. Check the disassembly code on compiler explorer: https://godbolt.org/z/S3ZX2Y
My interpretation is as follows: the standard states that reinterpret_cast
can convert between pointers/references of any types, but that accessing there resulting references might be UB (as per to aliasing rules). At the same time, converting the resulting value back to the original type is guaranteed to yield the original value.
Based on this, merely reinterepting a reference to float
as a reference to AddOne<float>
does not invoke UB. Since we never attempt to access any memory behind that reference as instance of AddOne<float>
, there is no UB here either. We only use the type information of that reference to select the correct implementation of add_one()
member function. The function itself coverts the reference back to the original type, so again, no UB. Essentially, this pattern is semantically equivalent to this:
template<typename T>
struct AddOne {
static T add_one(T const& x) {
return x + 1;
}
};
auto test(float x) {
return AddOne<Int>::add_one(x);
}
Am I correct or is there something I miss here?
Consider this as an academic exercise in exploring the C++ standard.
Edit: this is not a duplicate of When to use reinterpret_cast? since that question does not discuss casting this
pointer or using reinterpret_cast
to dispatch on the reinterpreted type.
No, that's definitely not legal. For a number of reasons.
The first reason is, you've got *this
dereferencing an AddOne<int>*
which doesn't actually point to an AddOne<int>
. It doesn't matter that the operation doesn't really require a dereference "behind the scenes"; *foo
is only legal if foo
points to an object of compatible type.
The second reason is similar: You're calling a member function on an AddOne<int>
which isn't. It likewise doesn't matter that you don't access any of AddOne
's (nonexistent) members: the function call itself is an access of the object value, running afoul of the strict aliasing rule.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With