0

I would like to iterate some code over a few maps that have different value types, but which are all derived from a base class, which I can guarantee is the first inherited class, and the classes are plain old data.

I could probably use templated functions, or use maps of pointers, but I would rather not.

This seems to work in the tests I perform. Is it valid code?

#include <iostream>
#include <map>

using std::map;

struct Base
{
    double a;
};

struct Derived : Base
{
};

map<int, Derived> mapOfDerived;
map<int, Base>    mapOfBase;

int main()
{
    mapOfDerived[2].a = 4;
    mapOfDerived[4].a = 5;
    mapOfBase   [1].a = 7;
    mapOfBase   [5].a = 1;
    
    for (auto& map_ptr :
        {   (map<int, Base>*) &mapOfDerived, 
            (map<int, Base>*) &mapOfBase
        })
    for (auto& [key, val] : *map_ptr)
    {
        std::cout << std::endl << key << " " << val.a;
    }

    return 0;
}
  • Take a look at [What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – Friedrich May 02 '23 at 06:15
  • 1
    @Friedrich no slicing is happening here, just plain undefined behaviour – Alan Birtles May 02 '23 at 06:29
  • @AlanBirtles you are of course right. However, the whole `std::map` (w/o ref or pointer) smells of slicing. It's not the issue here but may be the next pitfall OP runs into. – Friedrich May 02 '23 at 06:38
  • You should create an abstract baseclass (with getters/setters/methods) first and use that as base then you can store pointers to that abstract baseclass (interface) in the map. – Pepijn Kramer May 02 '23 at 07:09

3 Answers3

2

There are 2 problems here, even if your test code masks them:

  1. On a language point of view, you are using a pointer to a map<int, Base> to access a map<int, Derived>. They are different classes and have no inheritance relation between them, so that is not allowed by the language. It means that you are essentially invoking Undefined Behaviour. The language does not require any error message, but the outcome depends on implementation details of you current compiler. Said differently it may give expected results in one program and fail miserabily on a close one or even with the exact same source if you use a different compiler or different compilation options. But as you use C-style pointer conversion, the compiler has to accept this code.

  2. On an implementation point of view, the major risk is object slicing: if you write a Derived object into a map<int, Derived> but through a map<int, Base> pointer, you will probably never copy Derived specific fields. You code does not exhibit this problem because you only read elements and your derived class is a plain copy of its base (no additional field). But the risk of problem in more realistic code is high...

Said differently, this is not allowed by the language and it invokes Undefined Behaviour. (Un)fortunately, your code is too simplistic to exhibit any problem with your current compiler.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

No, that's undefined behavior.

You cannot cast a pointer to one class type to a pointer of another and use the result as if it was really pointing to an object of the target type, except if the classes are related by inheritance, which map<int, Derived> and map<int, Base> are not, or in a few very specific other situations, e.g. members of unions, which also don't apply.

Don't use C-style casts. They make the compiler do almost any type cast, regardless of whether the result is actually allowed to be used. Had you used a C++-style static_cast instead, the compiler would have correctly complained to you.

user17732522
  • 53,019
  • 2
  • 56
  • 105
0

No it is not. std::map<int,Base> and std::map<int,Derived> are completely different types.

bitmask
  • 32,434
  • 14
  • 99
  • 159