Skip to content
This repository has been archived by the owner on Jan 6, 2022. It is now read-only.

Cannot implicit convert to a retain_ptr of a base-class #7

Open
nordlow opened this issue Dec 3, 2018 · 0 comments
Open

Cannot implicit convert to a retain_ptr of a base-class #7

nordlow opened this issue Dec 3, 2018 · 0 comments

Comments

@nordlow
Copy link

nordlow commented Dec 3, 2018

The following code examples illustrates a current severe limitation with retain_ptr

#include <iostream>
#include <memory>
#include "sg14/memory.hpp"

using namespace std;

class Base
{
public:
    Base()
    {
    }
    mutable unsigned int _rc = 1;
};

class Sub : public Base
{
public:
    Sub(int x_)
        : Base(), x(x_)
    {
        cout << __PRETTY_FUNCTION__ << ":" << endl;
    }
    ~Sub() { cout << __PRETTY_FUNCTION__ << ":" << endl; }
public:
    int x;
};

void test_shared()
{
    using SharedBase = std::shared_ptr<const Base>;
    using SharedSub = std::shared_ptr<const Sub>;
    SharedBase base;
    SharedSub sub;
    base = sub;                 // compiles!
}

struct Base_traits final
{
    static void increment(const Base* x) noexcept
    {
        x->_rc += 1;
        cout << __PRETTY_FUNCTION__ << ": rc=" << x->_rc << endl;
    }
    static void decrement(const Base* x) noexcept
    {
        x->_rc -= 1;
        cout << __PRETTY_FUNCTION__ << ": rc=" << x->_rc << endl;
        if (x->_rc == 0)
        {
            delete x;
        }
    }
};

struct Sub_traits final
{
    static void increment(Sub* x) noexcept
    {
        x->_rc += 1;
        cout << __PRETTY_FUNCTION__ << ": rc=" << x->_rc << endl;
    }
    static void decrement(Sub* x) noexcept
    {
        x->_rc -= 1;
        cout << __PRETTY_FUNCTION__ << ": rc=" << x->_rc << endl;
        if (x->_rc == 0)
        {
            delete x;
        }
    }
};

using RetainBase = sg14::retain_ptr<const Base, Base_traits>;
using RetainSub = sg14::retain_ptr<const Sub, Base_traits>;

void test_retain()
{
    cout << "sizeof(Sub): " << sizeof(Sub) << endl;
    cout << "sizeof(RetainSub): " << sizeof(RetainSub) << endl;
    auto sub = RetainSub(new Sub(42));
    auto base = RetainBase(new Base());
    base = sub; // this errors
}

int main(__attribute__((unused)) int argc,
         __attribute__((unused)) const char* argv[],
         __attribute__((unused)) const char* envp[])
{
    test_shared();
    test_retain();
    return 0;
}

not present in std::shared_ptr. Namely the lack of implicit casting from a retain_ptr of an inherited class (Sub), to a retain_ptr of a base-class (Base) of Sub. Which result in the following compiler error

retain_ptr_test.cpp: In function ‘void test_retain()’:
retain_ptr_test.cpp:83:12: error: no match for ‘operator=’ in ‘base = sub’ (operand types are ‘sg14::retain_ptr<const Base, Base_traits>’ and ‘sg14::retain_ptr<const Sub, Base_traits>’)
In file included from retain_ptr_test.cpp:3:
sg14/memory.hpp:200:17: note: candidate: ‘sg14::retain_ptr<T, R>& sg14::retain_ptr<T, R>::operator=(const sg14::retain_ptr<T, R>&) [with T = const Base; R = Base_traits]’
sg14/memory.hpp:200:17: note:   no known conversion for argument 1 from ‘sg14::retain_ptr<const Sub, Base_traits>’ to ‘const sg14::retain_ptr<const Base, Base_traits>&’
sg14/memory.hpp:205:17: note: candidate: ‘sg14::retain_ptr<T, R>& sg14::retain_ptr<T, R>::operator=(sg14::retain_ptr<T, R>&&) [with T = const Base; R = Base_traits]’
sg14/memory.hpp:205:17: note:   no known conversion for argument 1 from ‘sg14::retain_ptr<const Sub, Base_traits>’ to ‘sg14::retain_ptr<const Base, Base_traits>&&’
sg14/memory.hpp:210:17: note: candidate: ‘sg14::retain_ptr<T, R>& sg14::retain_ptr<T, R>::operator=(std::nullptr_t) [with T = const Base; R = Base_traits; std::nullptr_t = std::nullptr_t]’
sg14/memory.hpp:210:17: note:   no known conversion for argument 1 from ‘sg14::retain_ptr<const Sub, Base_traits>’ to ‘std::nullptr_t’

when compiled with gcc-8 and -std=c++17.

How can I change sg14/memory.hpp to allow this assignment to be allowed?

My two current proposals are

    template <class U>
    using enable_if_sub = std::enable_if_t<std::is_base_of_v<T, U>>;

    template <class S, class = enable_if_sub<S>>
    retain_ptr& operator = (retain_ptr<S, R> &&that) {
        if (*this) { traits_type::decrement(this->get()); } // drop
        ptr = that.detach();
        return *this;
    }

or

    template <class S, class = enable_if_sub<S>>
    retain_ptr& operator = (retain_ptr<S, R> &&that) {
        *this = *reinterpret_cast<retain_ptr<T, R> *>(&that);
        return *this;
    }

or


    template <class S, class = enable_if_sub<S>>
    retain_ptr& operator = (retain_ptr<S, R> &&that) {
        reinterpret_cast<retain_ptr<T, R> *>(&that)->swap(*this);
        return *this;
    }

but all three make AddressSanitizer complain as

=================================================================
==4647==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x602000000010 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   8 bytes;
  size of the deallocated type: 4 bytes.
    #0 0x563de6218b28 in operator delete(void*, unsigned long) (/home/per/.emacs.d/auto-builds/gcc-8/AddressSanitized-Debug/home/per/Work/justcxx/retain_ptr_test+0xd7b28)
    #1 0x563de6254df4 in Base_traits::decrement(Base const*) /home/per/Work/justcxx/retain_ptr_test.cpp:52
    #2 0x563de6254f2b in sg14::retain_ptr<Base const, Base_traits>::~retain_ptr() sg14/memory.hpp:197
    #3 0x563de62547fc in test_retain() /home/per/Work/justcxx/retain_ptr_test.cpp:83
    #4 0x563de62548cf in main /home/per/Work/justcxx/retain_ptr_test.cpp:92
    #5 0x7f21d42c1b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #6 0x563de6149569 in _start (/home/per/.emacs.d/auto-builds/gcc-8/AddressSanitized-Debug/home/per/Work/justcxx/retain_ptr_test+0x8569)

0x602000000010 is located 0 bytes inside of 8-byte region [0x602000000010,0x602000000018)
allocated by thread T0 here:
    #0 0x563de6217570 in operator new(unsigned long) (/home/per/.emacs.d/auto-builds/gcc-8/AddressSanitized-Debug/home/per/Work/justcxx/retain_ptr_test+0xd6570)
    #1 0x563de625478a in test_retain() /home/per/Work/justcxx/retain_ptr_test.cpp:82
    #2 0x563de62548cf in main /home/per/Work/justcxx/retain_ptr_test.cpp:92
    #3 0x7f21d42c1b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

SUMMARY: AddressSanitizer: new-delete-type-mismatch (/home/per/.emacs.d/auto-builds/gcc-8/AddressSanitized-Debug/home/per/Work/justcxx/retain_ptr_test+0xd7b28) in operator delete(void*, unsigned long)
==4647==HINT: if you don't care about these errors you may set ASAN_OPTIONS=new_delete_type_mismatch=0
==4647==ABORTING

. What am I doing wrong?

Update:

I think I found a solution

https://github.com/nordlow/justcxx/blob/master/sg14/memory.hpp

in use here

https://github.com/nordlow/justcxx/blob/master/retain_ptr_test.cpp

without any complaints from AddressSantitizer.

@nordlow nordlow changed the title Cannot use polymorphic types Cannot use implicit convert between retain_ptr's of polymorphic types Dec 3, 2018
@nordlow nordlow changed the title Cannot use implicit convert between retain_ptr's of polymorphic types Cannot implicit convert to a retain_ptr of a base-class Dec 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant