1

I am trying to extend std::map's std::pair entries with some extra functionality and want to cast such pairs to a child class of pair. Note that this is the "wrong" direction of polymorphism, the child is not the parent. But since the memory layout should be identical as long as I am not introducing additional members in the child class, I am wondering if this is valid.

Here is a minimal example:

#include <iostream>
#include <map>
#include <string>

class Concatenator : public std::pair<const std::string, std::string> {
public:
  operator std::string() const { return first + ", " + second; }
};

int main() {
  std::map<std::string, std::string> m{{"hello", "world"}};

  // Is this defined behaviour?
  Concatenator &c{*static_cast<Concatenator *>(&*m.begin())};
  std::cout << std::string{c} << std::endl;
}
phinz
  • 1,225
  • 10
  • 21
  • 2
    What's wrong with overloading the `<<` operator so it produces the desired output? If this is too _implicit_ for you, you might also write a conversion function. – Friedrich Jan 26 '23 at 17:30
  • @Friedrich The usage of cout is just for this example, I like to have some object that has convenience function and uses an underlying pair object of a map. You are right however, that there are easy workarounds like writing a wrapper class that holds a reference to such a pair. However, when thinking about the problem, this came option came to my mind and now I just want to know it is a valid option. – phinz Jan 26 '23 at 17:34
  • Similar question (with a member not a base class, and with `reinterpret_cast` instead of your [definitely broken `static_cast`](https://stackoverflow.com/a/6322985)): https://stackoverflow.com/q/49835673 – Artyer Jan 26 '23 at 17:46
  • @Artyer This question is not entirely similar because A is a member of B there and it can be padded or aligned. – phinz Jan 26 '23 at 17:53

2 Answers2

4

But since the memory layout should be identical as long as I am not introducing additional members in the child class...

No, that's wrong. That is undefined behavior.

Even without multiple inheritance, there is no guarantee that the memory layout is identical. Even if the memory layout was identical, then it is still undefined behavior. Do not do this.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • OK and if I also throw in the additional restriction that I do not use multiple inheritance? Is it at least correct that the memory layout is identical? – phinz Jan 26 '23 at 17:36
  • no, unfortunately that's still undefined behavior no matter what. – Mooing Duck Jan 26 '23 at 17:46
  • OK I won't do this, thank you. Is it possible to give a counter example where the memory layout is not identical with packed or padding specifiers? Or is it just not guaranteed? – phinz Jan 26 '23 at 17:51
  • @phinz C++ only guarantees the order in specific cases, which are completely irrelevant, because the static_cast is undefined behavior regardless. It's also almost completely unnecessary. – Mooing Duck Jan 26 '23 at 17:54
0

I think this is well defined:

#include <iostream>
#include <map>
#include <string>

class Concatenator : public std::pair<const std::string, std::string> {
public:
    using base = std::pair<const std::string, std::string>;
    Concatenator(const base& o) : base(o) {}
    operator std::string() const { return first + ", " + second; }
};

int main() {
  std::map<std::string, std::string> m{{"hello", "world"}};

  Concatenator c = *m.begin();
  std::cout << std::string{c} << std::endl;
}
sklott
  • 2,634
  • 6
  • 17