How to cast to privately derived child type?
Asked Answered
S

2

0

The following is an attempt at implementing a shared pointer with a modified semantics of operator==:

template <typename T>
struct deref_shared_ptr: private std::shared_ptr<T> {
    using Base = std::shared_ptr<T>;
    // ... using statements to include functionality from the base.

    bool operator==(const deref_shared_ptr rhs) const {
        return (**this == *rhs);
    }
};

I am struggling with implementing an equivalent of std::make_shared for this type. This is my attempt:

template< class T, class... Args >
deref_shared_ptr<T> make_deref_shared( Args&&... args ) {
    return reinterpret_cast<deref_shared_ptr<T>>(std::make_shared<T>(args...));
}

This does not work: the compiler (g++ 5.4.0) complains about an invalid cast. Why does it not work and what should I do instead of this cast?

Scibert answered 31/7, 2016 at 8:14 Comment(7)
Use friend. It's not a sin.Plantagenet
@n.m. Do you mean having a private constructor that takes an std::shared_ptr argument?Scibert
Sorry, didn't look at your code too closely. Casting to a derived type (not a pointer) is only possible if the derived type has a constructor that takes the parent type, or the parent type has a right type conversion operator. So yes, you do need a constructor. Whether you make it private or not is up to you,Plantagenet
@n.m. That makes the below answer wrong, doesn't it?Scibert
It looks like it is, yes.Plantagenet
@n.m. It looks like people who upvoted it do not think so.Scibert
Evidently so. Perhaps they all misread the question. I know I did initially.Plantagenet
T
2

You see this compiler error message because the reinterpret_cast cannot make casts through the private inheritance. Please check the following themes on this topic: difference between c++ casts, conversion which may be handled by c-style cast only.

The only way to go through the private inheritance is the c-style cast. So, changing your example as follows makes your example work:

template< class T, class... Args >
deref_shared_ptr<T> make_deref_shared(Args&&... args) {
    return (deref_shared_ptr<T>)(std::make_shared<T>(args...));
}

The c-style cast is not safe in the general case since it may work incorrectly in cases of multiple inheritance and some other cases, but AFAIK it's safe in this case.

Topminnow answered 31/7, 2016 at 8:21 Comment(12)
Would the answer be same the other way around (i.e. to cast from child to base) in this case? I think that I will need that cast for implementing constructors of the new type.Scibert
Yes, it should be. c++ casts have no information about private inheritanceTopminnow
The solution does not work. The compiler complains about the absence of a constructor that takes a std::shared_ptr<T> argument, which I obviously do not want to provide.Scibert
You mean the example from the answer or the implementation of constructors of the new type?Topminnow
That's very strange. This code compiles both on my machine (VS 2015) and on ideone (g++5.1): ideone.com/D8ZxrhTopminnow
I was able to compile the following implementation, but not the one without going through a pointer: ` auto res = std::make_shared<T>(args...); return *(deref_shared_ptr<T> *)(&res);`Scibert
Let us continue this discussion in chat.Topminnow
Interestingly, the solution with going through pointer works even with reinterpret_cast. I am on chat to continue. Thank you!Scibert
@Scibert "I was able to compile the following implementation" no problem with compiling it, but the behaviour is undefined.Plantagenet
@Scibert You are casting a pointer to a base object to a pointer to a derived object, without having a derived object anywhere in sight. Then you proceed to dereference the resulting pointer. What makes you think this could possibly be defined?Plantagenet
@n.m. My derived type has exactly the same layout as the base type. So, res is the derived object, only I am fighting the compiler to agree with me on this point...Scibert
@Scibert "My derived type has exactly the same layout as the base type." Even if this is true, it doesn't create an exception for the rules. "res is the derived object" It's a popular misconception that has no basis in reality. See #20518737Plantagenet
C
2

I suggest your deref_shared_ptr to implement a constructor that receive a std::shared_ptr as parameter, so the conversion would be possible. Right now your compiler has no idea how to make a deref_shared_ptr from a std::shared_ptr. This is exactly what we will teach your compiler to do.

I noticed you add a custom operator== to compare correctly your type with a std::shared_ptr. Here we want to do the same thing but with constructor. We want a constructor that construct correctly with your type with a std::shared_ptr!

The constructor would look like this:

template<typename T>
struct deref_shared_ptr : private std::shared_ptr<T> {
    // An alias to the parent may help msvc with templated parent types
    using parent = std::shared_ptr<T>; 

    // Implement a constructor that takes shared_ptr by copy and move
    deref_shared_ptr(const parent& ptr) : parent{ptr} {}
    deref_shared_ptr(parent&& ptr) : parent{std::move(ptr)} {}

    // stuff...
};

Then, the make function becomes trivial to implement:

template<typename T, typename... Args>
deref_shared_ptr<T> make_deref_shared(Args&&... args) {
    // Don't forget perfect forwarding here!
    return std::make_shared<T>(std::forward<Args>(args)...);
}

EDIT:

Alternatively, if your constructors are not doing any operation, you can make use of inheriting constructors:

template<typename T>
struct deref_shared_ptr : private std::shared_ptr<T> {
    using parent = std::shared_ptr<T>; 

    // Implement constructors
    using parent::parent;

    // stuff...
};

That would simplify constructor implementations and will make your type compatible by construction with std::shared_ptr.

Columbarium answered 31/7, 2016 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.