简体   繁体   中英

Return unique_ptr member variable from proxy class with operator()

I would like to make a wrapper class (in this case a not null check, but it could be other checks).

Is there any way to extract member variable through operator() that would make it possible to move out the member variable. The use case is a std::unique_ptr<>

This is the use case

#include <memory>

struct S {
    int x = 0;
    S(int x): x(x) {}
};

template <typename Type>
class NotNull
{
public:
    NotNull(Type value): m_value(std::move(value)) {}

    operator Type&() // <--------------------------------- this is the question
    {
       assertIsNotNull();
       return m_value;
    }


    typename std::pointer_traits<Type>::element_type *get() {
        return m_value.get();
    }

    private:
    void assertIsNotNull() {}

    Type m_value;
};

And this is what needs to work

// Test code
int main() {
    {
        NotNull<S *> x {new S{10}};
        auto y = x; // This works
        delete y;
    }

    {
        NotNull<std::shared_ptr<S>> x{std::make_shared<S>(10)};
        auto y = x; // This works
    }
        
    {
        NotNull<std::unique_ptr<S>> x{std::make_unique<S>(10)};
        S* y = x.get(); // This does work, and needs to work as expected
                        // that is _not_ move the member
    }

    {
        NotNull<std::unique_ptr<S>> x{std::make_unique<S>(10)};
        auto y = std::move(x); // This copies the whole class
    }

    {
        NotNull<std::unique_ptr<S>> x{std::make_unique<S>(10)};
        std::unique_ptr<S> y = std::move(x); // <----------------- This does not work
    }
}

The compiler seems to not understand that I want to convert to a unique_ptr inside of the std::move call.

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = S; _Dp = std::default_delete<S>]'
   57 |         std::unique_ptr<S> y = std::move(x); 
      |     

Compiler explorer link

Is it possible to use a class as a proxy for std::unique_ptr somehow and get this kind of syntax for moving out member variables.

PS. As you might have guessed I cannot rely on for example gsl::not_null, because this functionality does not exist there to my knowledge. DS.

The compiler understands that you want to convert to a unique_ptr& (ie Type&) just fine. The problem arises when you assign the result of that conversion to your local unique_ptr object: since its an lvalue reference, the compiler tries to invoke the copy constructor (not the move constructor, which requires an rvalue reference), but since unique_ptr deleted its copy constructor, you get the error.

What you probably wanted to do in that line is to convert to a unique_ptr&& (ie an rvalue reference). To do that, you can overload your conversion operator based on ref-qualifiers :

operator Type&() & // converts to lvalue ref if invoked on lvalue
{
   assertIsNotNull();
   return m_value;
}

operator Type&&() && // converts to rvalue ref if invoked on a temporary
{
   assertIsNotNull();
   return std::move(m_value);
}

This way, the conversion operator will convert to the same type of reference that it was invoked on (ie lvalue of used from a normal variable, and rvalue if used on a temporary or moved object).

You can use reference qualifiers to make separate versions of a member function depending on whether the object is an lvalue or rvalue reference, like so:

#include <memory>
#include <iostream>

struct S {
    int x = 0;
    S(int x): x(x) {}
};

template <typename Type>
class NotNull
{
public:
    NotNull(Type value): m_value(std::move(value)) {}

    operator Type&()
    {
    assertIsNotNull();
    return m_value;
    }

    auto get() &
    {
        std::cout << "lvalue \n";
        return m_value.get();
    }

    auto get() &&
    {
        std::cout << "rvalue \n";
        auto retval =  m_value.get();
        m_value.release();
        return retval;
    }

    private:
    void assertIsNotNull() {}

    Type m_value;
};

int main()
{
    {
        NotNull<std::unique_ptr<S>> x{std::make_unique<S>(10)};
        S* y = x.get(); // This does work, and needs to work as expected
                        // that is _not_ move the member
    }
    {
        NotNull<std::unique_ptr<S>> x{std::make_unique<S>(10)};
        std::unique_ptr<S> y = std::unique_ptr<S>(std::move(x).get());  // Is this what you want?
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM