0

Every time I use reinterpret_cast I feel like I'm taking a long walk off a short pier.

Is it possible to allow for implicit pointer conversion between templated types with the same underlying structure such as the below?

template <typename T>
struct S {};

int main() {
    S<int>* foo = new S<int>;
    S<double>* foo1 = foo;
   return 1;
}

This gives an error of

<source>: In function 'int main()':
<source>:9:23: error: cannot convert 'S<int>*' to 'S<double>*' in initialization
    9 |     S<double>* foo1 = foo;
      |                       ^~~
      |                       |
      |                       S<int>*

Which makes since, we never specified any type of conversion operator between the two instances

But I'm having trouble understanding if there is a conversion operator that can allow for implicit conversion between these two types. For instance, below is every constructor, assigner, and conversion operator I can think of and it doesn't seem to do that job

#include <iostream>
struct Arg {};

template <typename T>
struct S {
    // All the usually generated constructor/destructor/assigners
    S() { puts("\t\tDefault Constructor");}
    S(Arg) { puts("\t\tValue Constructor");}
    ~S() { puts("\t\tDestruct");}
    S(const S<T>&) { puts("\t\tCopy construct");}
    S(S<T>&&) {puts("\t\tMove construct");}
    S<T>& operator=(const S<T>&) {puts("\t\tCopy Assign"); return *this;}
    S<T>& operator=(S<T>&&) {puts("\t\tMove Assign"); return *this;}
    // Constructors/assigner/conversions to alt S<>
    template <typename P>
    S(const S<P>&) { puts("\t\tCopy construct");}
    template <typename P>
    S(S<P>&&) {puts("\t\tMove construct");}
    template <typename P>
    S<T>& operator=(const S<P>&) {puts("\t\tCopy Assign"); return *this;}
    template <typename P>
    S<T>& operator=(S<P>&&) {puts("\t\tMove Assign"); return *this;}
    template <typename P>
    operator S<P>() { puts("Conversion operator"); return S<P>();}
    template <typename P>
    operator S<P>*() {puts("Pointer Conversion operator"); return new S<P>();}
};

int main() {
    S<int>* foo = new S<int>;
    S<double>* foo1 = foo;
   return 1;
}

https://godbolt.org/z/K6sEBS

But that gives the same error

If I do

int main() {
    S<int>* foo = new S<int>;
    S<double>* foo1 = *foo;
   return 1;
}

That does work by using the pointer conversion operator. Inside of the pointer conversion operator I could use reinterpret_cast<S<P>*>(this), though I worry whether that is safe.

And of course reinterpret_cast works, though reading the rules it's not clear to me whether this is UB.

int main() {
    S<int>* foo = new S<int>;
    S<double>* foo1 = reinterpret_cast<S<double>*>(foo);
   return 1;
}

So if there is a way to make the following valid I'd love to know!

S<int>* foo = new S<int>;
S<double>* foo1 = foo;

I'm using C++14 so anything on a higher standard than that and I'll have a difficult time accepting it.

Thank you for reading this!

Edit: As Sam points out in the comments I'm really not sure if this is possible, if not then my Q is just whether the reinterpret_cast is safe in this situation

Edit Edit: For further context, I work on an open source software project where the implementation of our scalar type cannot hold an integral type. So the underlying implementation of S<int> is the same as S<double> aka all integral template types are treated by the class as doubles. The implementation S is used in a PIMPL idiom ala

template <typename T>
class Q {
   S<T>* s_type_; // if int T will treat T as a double
   // stuff...
   template <typename P, enable_if_t<is_convertible_v<P&, T>>* = nullptr>
   Q(S<P>* x) : s_type_(x) {} // this line fails for S<double>*
};

This is not the exact code but it's enough to showcase the problem. So the question is pertaining to the constructor for Q that takes in a pointer to an S<int> and whether it's valid to assign that pointer to an S<double>.

Steve Bronder
  • 926
  • 11
  • 17
  • 2
    Short answer: unfortunately no, it's not. C++ does not work this way. – Sam Varshavchik Jun 27 '20 at 20:07
  • Dang, yeah I figured. It makes sense to now allow it, 99% of the time this would probably lead to an error – Steve Bronder Jun 27 '20 at 20:10
  • It isn't clear what you are trying to achieve. `S` doesn't use its template parameter `T`. Why is it there in the first place, and why, after deliberately placing it there, are you trying to make your program ignore the fact that it's there? It looks like a big fat case of XY problem to me. At any rate, as the things stand now, `S` and `S`are completely unrelated types and accessing one as if it were the other is UB, plain and simple, there's no way around it in any standard. If you want to access an object, access it through an lvalue of **its** type. Full stop, end of story. – n. m. could be an AI Jun 27 '20 at 20:12
  • @n.'pronouns'm. this is a minimal example. I work on an open source software project where S and S both hold two members of type double. S and S will always have the same underlying structure / members. The exact types I'm working with are more complex but the minimal example above captures the issue. Why specifically is the `reinterpret_cast` from S to S UB? – Steve Bronder Jun 27 '20 at 20:30
  • @n.'pronouns'm. updated to include more context – Steve Bronder Jun 27 '20 at 20:48
  • 1
    I closed this question as a duplicate. The linked question is, I believe, essentially the same question. – cdhowie Jun 27 '20 at 20:56
  • @cdhowie yes thank you! It appears my google-fu failed me – Steve Bronder Jun 27 '20 at 21:01
  • 1
    @Steve_Corrin No worries. It took me a lot longer than usual to find it, actually. I'm not surprised that a cursory search wouldn't turn it up. – cdhowie Jun 27 '20 at 21:02
  • @cdhowie actually, reading that question it seems like it pertains to using `reinterpret_cast` on an object (Mary) to a reference type (Ashley&) while my Q is pointer to pointer conversion aka Mary* to Ashley*. I think they are very similar but the answer there quotes [expr.reinterpret.cast]/11 and I think the answer to my Q might be [expr.reinterpret.cast]/7 https://timsong-cpp.github.io/cppwp/n4659/expr.reinterpret.cast#7 – Steve Bronder Jun 27 '20 at 21:12
  • 1
    @Steve_Corrin They're both saying the same thing, roughly. In fact, the reference case calls back to the pointer case, so they are effectively the same: _"a reference cast `reinterpret_­cast(x)` has the same effect as the conversion `*reinterpret_­cast(&x)`"_. The question is about whether the converted reference, or dereferenced converted pointer can be used without UB -- nobody is questioning whether the cast itself is legal (it is). The answer, I believe, is the same for both: no, there is UB. – cdhowie Jun 27 '20 at 21:32
  • @cdhowie aight that's reasonable thanks for the clarification! – Steve Bronder Jun 27 '20 at 21:34
  • 1
    If you need the two object to contain the same members, make them of the same type. Why do you need them to have different types? – n. m. could be an AI Jun 28 '20 at 05:55
  • @n.'pronouns'm. yeah so looking at the other answer I think the answer is just to fail if users try to write funcitons that template with integral types – Steve Bronder Jun 28 '20 at 18:56

0 Answers0