Как отличить от дочернего типа, полученного в частном порядке?

Ниже приведена попытка реализовать общий указатель с модифицированной семантикой 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);
    }
};

Я борюсь с реализацией эквивалента std::make_shared для этого типа. Это моя попытка:

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...));
}

Это не работает: компилятор (g++ 5.4.0) жалуется на недопустимый листинг. Почему это не работает и что мне делать вместо этого приведения?

Ответ 1

Вы видите это сообщение об ошибке компилятора, потому что reinterpret_cast не может выполнять касты через частное наследование. Пожалуйста, ознакомьтесь со следующими темами по этой теме: разница между С++ casts, преобразование, которое может обрабатываться только c-style cast.

Единственный способ пройти через наследование private - это приведение в стиле c. Итак, изменив ваш пример следующим образом, ваш пример работает:

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

Приведение в стиле c не является безопасным в общем случае, поскольку оно может работать некорректно в случаях множественного наследования и некоторых других случаях, но AFAIK в этом случае безопасно.

Ответ 2

Я предлагаю вашему deref_shared_ptr реализовать конструктор, который получает параметр std::shared_ptr as, поэтому преобразование будет возможно. Прямо сейчас ваш компилятор понятия не имеет, как сделать deref_shared_ptr с std::shared_ptr. Это именно то, чему мы научим ваш компилятор.

Я заметил, что вы добавили пользовательский operator==, чтобы правильно сравнить ваш тип с std::shared_ptr. Здесь мы хотим сделать то же самое, но с конструктором. Мы хотим, чтобы конструктор правильно построил ваш тип с помощью std::shared_ptr!

Конструктор будет выглядеть так:

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...
};

Затем функция make становится тривиальной для реализации:

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:

В качестве альтернативы, если ваши конструкторы не выполняют никакой операции, вы можете использовать наследующие конструкторы:

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

    // Implement constructors
    using parent::parent;

    // stuff...
};

Это упростит реализации конструктора и сделает ваш тип совместимым по конструкции с помощью std::shared_ptr.