Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reinterpret this in C++: legal or not?

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.

like image 808
MrMobster Avatar asked Mar 02 '19 10:03

MrMobster


Video Answer


1 Answers

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.

like image 95
Sneftel Avatar answered Oct 21 '22 11:10

Sneftel