0

Is it safe to static_cast a concrete base class object, to a reference to a derived concrete class object when the derived class has no additional data members? Here is a simple example for illustration:

#include <array>

struct Foo : std::array<int,3>
{
  Foo() : std::array<int,3>{1,2,3} {}

  void hello() { std::cout << "Hello\n"; }
};

Foo test()
{
  std::array<int,3> a{4,5,6};
  return static_cast<Foo&>(a);
}
user2023370
  • 10,488
  • 6
  • 50
  • 83
  • 5
    No, this is UB. UB is the opposite of safe – NathanOliver Mar 08 '21 at 18:38
  • You need a conversion operator. Dark magic like this will only cause problems. – tadman Mar 08 '21 at 18:52
  • Is the danger in general, or can you see it already in this example? Do you have a reference for this being UB? – user2023370 Mar 08 '21 at 19:00
  • 1
    see: https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – NathanOliver Mar 08 '21 at 19:07
  • I see. I hadn't thought of it that way. We are indeed referencing the data of `a` through both `a` (of type `std::array` and also through a `Foo&` returned by the `static_cast`. What if `test` was simpler and only used an unnamed temporary: say just `return static_cast(std::array{4,5,6});` ? – user2023370 Mar 08 '21 at 19:36
  • 1
    @user2023370 the problem is that you are casting a type `A` into a *reference to an unrelated type* `B` -- so no, `static_cast(std::array<...>{...})` will still also **not be legal**. `std::array` is **not** a `Foo` -- so you cannot cast a reference `std::array` into a reference to `Foo`. You will have to construct a `Foo` to take its reference -- even if that just means you change `a ` to be a `Foo` object. – Human-Compiler Mar 08 '21 at 20:37

1 Answers1

1

As others have noted, this is undefined behavior (so unsafe) even though it is likely to work for POD types with no virtual members or bases. It's easy enough to do it safely by just defining the appropriate constructor:

#include <array>

struct Foo : std::array<int,3>
{
  Foo() : std::array<int,3>({1,2,3}) {}

  void hello() { std::cout << "Hello\n"; }

  Foo(const std::array<int, 3> &a) : std::array<int,3>(a) {}
};

Foo test()
{
  std::array<int,3> a{4,5,6};
  return a;
}

and then no cast is actually needed.


If for some bizarre social reason (assignment requirements?) you're not allowed to change the definition of Foo, you can get around this by using an adaptor class:

struct Foo : std::array<int,3>
{
  Foo() : std::array<int,3>({1,2,3}) {}

  void hello() { std::cout << "Hello\n"; }
};

struct FooAdaptor : public Foo {
  FooAdaptor(const std::array<int, 3> &a) {
    *this = a;
  }
};

Foo test()
{
  std::array<int,3> a{4,5,6};
  return FooAdaptor(a);
}

but I would not recommend this for real code

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • In my case I am not able to add the constructor. – user2023370 Mar 08 '21 at 20:29
  • Is it still UB if the body of `test` is `return std::array{4,5,6};`? – user2023370 Mar 08 '21 at 20:32
  • @user2023370 **assuming this does an implicit construction of `Foo`**, that would be valid. The issue in the code in your question is strictly to do with casting to a reference of an unrelated type (since `a` is not a `Foo`) – Human-Compiler Mar 08 '21 at 20:34
  • Sorry, typing on my phone now. That won't compile with the original code. I meant to say: `return static_cast(std::array{4,5,6});` ? – user2023370 Mar 08 '21 at 20:39
  • 1
    No, that would not be valid. I left a comment on the question explaining why. In short `a` is **not** a `Foo` -- so you can't take a reference to `Foo` of any kind (whether rvalue or lvalue). – Human-Compiler Mar 08 '21 at 20:41