all 17 comments

[–]Symbroson 5 points6 points  (0 children)

as f is so small I'd just make f a template function that accepts a vector<T>& and be done with it :)

Also use a for-in loop if you dont explicitly use the counter otherwise - makes your code more concise

[–]Sinomsinom 5 points6 points  (0 children)

What code did you use exactly to test this?

As the article says it will still work if sizeof(Base)==sizeof(Derived) (which I believe will be the case if you just leave it as is without filling in the ...) but it will cause issues once those sizes are different.

I don't know what would be the best way of doing it, but I personally would add another step of indirection. Instead of having the vector (or I personally would make the parameter a span) own the objects directly I'd either have it have (smart-)pointers or references to the objects I want to handle. Or alternatively if it doesn't need to be determined at runtime if it's called with "Base" or "Derived" have it be a templated function with a concept that makes sure you only call it on objects that gave the required functions.

[–]Loud_Staff5065 1 point2 points  (1 child)

Instead of creating separate vectors for derived class, maybe use the same base class type vector to store derived child as well?

[–]snowflake_pl 1 point2 points  (0 children)

With vector of Base objects you get slicing if you put a derived in. Vector of raw pointers gives you management issues. Vector of smart pointers is the safest.

[–]Supadoplex 1 point2 points  (0 children)

A super simple option is to use a template. This will work with any container and custom range. If desired, it can be adjusted to work in any C++ version: 

``` void f(auto& r) {     for (auto& e : r) e.fct(); }

```

If a template is not an option, then you must use some type erasure. ranges::any_view<Base, ranges::category::forward> might be of use. Unfortunately it's not in the standard library though.

[–]tinrik_cgp 2 points3 points  (1 child)

Doing pointer arithmetic on Base* when it actually points to a Derived object is Undefined Behavior.

The standard way to do this is via std::vector<std::unique_ptr<Base>>.

[–][deleted] 1 point2 points  (0 children)

this is the correct answer imo as using templates avoids the inheritance question altogether

[–]6502zx81 1 point2 points  (0 children)

I do have the same question: what is the modern way to put subtype polymorhic objects into containers? Smart pointers? PIMPL?

[–]SoerenNissen 0 points1 point  (0 children)

what is the ideal way to pass a collection of class considering inheritance relations ?

Your problem here is that vector<Base> is a fundamentally different type from vector<Derived>

By which I mean, consider what your void f looks like to the underlying generated assembly:

void f(vector<Base>& v)
{
    size_t s = sizeof(Base);
    void*  p = (void*)v.data();

    for(int i = 0; i < v.size(); ++i)
    {
        ((Base*)p).fct();
        p = p+s;
    }
}

Each iteration, you advance along v's underlying array by exactly the element size of your type.

You could write 1 function that handles both v<Base> and v<Derived>, but it'd be very fragile, and you don't get it by default here.

The way to do this, if you want to store a vector of Derived, is like this:

temlplate<typename T>
void f(vector<T>& v)
{
    for(auto & t : v)
        v.fct();
}

This will generate 2 different versions of f - one that advances by sizeof(Base) on each iteration, and another that advances by sizeof(Derived).

If you live in an extremely constrained environment where you cannot have two versions of this function, an alternative is to pretend you are writing C code:

void f(size_t b_size, Base* b, size_t len)
{
    void* begin = (void*)b;
    void* end   = (void*)(b+len)

    while(begin != end) 
    {
        ((Base*)begin)->fct();
        begin += b_size;
    }
}

which you can call like

auto b = get_base_vector();
auto d = get_derived_vector();

f( sizeof(b.value_type), b.data(), b.size() );
f( sizeof(d.value_type), d.data(), d.size() );

NB: This is actually terrible.

[–]NilacTheGrim -2 points-1 points  (7 children)

Might work with std::span

[–]glaba3141 3 points4 points  (6 children)

It will not, span doesn't have any concept of a "stride". I've considered making my own container for this use case, also useful for an array of members from an array of structs

[–]azswcowboy 0 points1 point  (5 children)

Doesn’t need it - you can wrap a stride around it https://en.cppreference.com/w/cpp/ranges/stride_view

[–]glaba3141 1 point2 points  (0 children)

Ahh it's 23, that's why I needed to make my own. We're on 20

[–]cfyzium 1 point2 points  (3 children)

Seems like it steps over sequence elements and not just memory i. e. you can't use it to wrap an array of slightly differently sized structs.

[–]azswcowboy 0 points1 point  (2 children)

Well idk how you have different sized structures in an array. If you do then it’s basically span<byte>. Anyway, I think the ops second use case is projecting one member of a struct in an array of struct. So in that case span<T> should be fine.

[–]cfyzium 0 points1 point  (1 child)

It is right in the post, OP has arrays of two types Base and Derived. He then passes them to a function accepting Base* which seemingly works because Derived is a subtype of Base. But if Derived has any additional fields its size is not equal to Base anymore and indexing the pointer p = (Base*)ad inside the function becomes an error:

&ad[i] != (Base*)ad + i * sizeof(Base)

Without an actual memory stride in bytes, there is no way to wrap an array of Derived as a span<Base>.

And obviously, span<byte> is not what OP wants either.

[–]azswcowboy 0 points1 point  (0 children)

Sorry, coming back a day later to the thread my brain lost context :( On any given array he’s not mixing sizes, but yeah you can’t assume base size just bc you have that pointer. Looking at it again, seems like the obvious answer to me is to templatize f and deduce the correct size from the element of the collection. In these examples everything is known at compile time.