Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reinterpret_cast vector of pointers to vector of pointers to base class

Tags:

Consider the following piece of code

#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>

struct Base {
    int x;

    Base(int x) : x(x) {}
};

struct Derived : public Base {
    int y, z;

    Derived(int x) : Base(x), y(x + 1), z(x + 2) {}
};

void update(const std::vector<std::shared_ptr<const Base>>& elements) {
    for (const auto elem : elements) {
        std::cout << elem->x << "\n";
    }
}

int main(int, char**) {
    std::vector<std::shared_ptr<Derived>> elements(4);

    {
        int ctr = 0;
        std::generate(begin(elements), end(elements), [&ctr]() { return std::make_shared<Derived>(++ctr); });
    }

//    update(elements); // note: candidate function not viable: no known conversion from 'vector<shared_ptr<Derived>>' to 'const vector<shared_ptr<const Base>>' for 1st argument
    update(reinterpret_cast<std::vector<std::shared_ptr<const Base>>&>(elements));  // ok

    return 0;
}

My question is if using reinterpret_cast to cast from std::vector<std::shared_ptr<Derived>> to std::vector<std::shared_ptr<const Base>>& is feasible and accepted by standard.

I have compiled the code with clang-3.8 and gcc-6.1 with -fsanitize=undefined and it looks like it's ok. However, I seem unable to find a proper explanation on cppreference.

Of course I can easily create an appriopriate function but it's longer than one-line reinterpret_cast and requires a temporary vector.

void update(const std::vector<std::shared_ptr<Derived>>& elements) {
    std::vector<std::shared_ptr<const Base>> casted(elements.size());
    std::copy(begin(elements), end(elements), begin(casted));
    update(casted);
}
like image 377
Rafal Avatar asked Jul 23 '16 06:07

Rafal


1 Answers

Templates in general and containers (I treat shared_ptr as a special form of container) are not covariant in C++. This means that if you have two types Base and Derived < Base and a template<typename T> class X {};, X<Base> and X<Derived> are two completely different things and not in any form of relationship.

In your case, you have an object of type std::vector<std::shared_ptr<Derived>> and then create a std::vector<std::shared_ptr<const Base>>& which is then used to access it. I think this has two issues:

  1. You cast an object of type vector into a reference type. I really wonder why this works.
  2. You access the object through a reference of unrelated different type. I think this violates the strict aliasing rule and is thus undefined behavior.

If you compile your code with gcc -fstrict-aliasing, the compiler will assume that your program is compliant with the rule and optimize it. It will generate a warning:

> Start prog.cc: In function 'int main(int, char**)': prog.cc:33:80:
> warning: dereferencing type-punned pointer will break strict-aliasing
> rules [-Wstrict-aliasing]
>      update(reinterpret_cast<std::vector<std::shared_ptr<const Base>>&>(elements));  // ok
like image 67
Jens Avatar answered Sep 28 '22 02:09

Jens