Search code examples
c++qtclassqt5

How to determine whether the return value is used as lvalue or rvalue?


I'm working on a project using Qt that puts me into this case: I need to create a class where the QVariant operator[] (someType) is overloaded, and both reading and writing effects of it need implementing.

The general idea is to return a ref, thus the value can be changed whenever the user wants. But the problem is, I have to use a class written by other people to manage my data, which only provides 2 functions - QVariant value() to read, and setValue(QVariant) to write, respectively.

So, if I want to achieve one-time call for both reading and writing, I need to determine whether the object returned by operator[] is used as an lvalue or rvalue.

Are there any ways?


Solution

  • you need to return a custom "reference" type, that overrides operator= to be assignable with QVariant and operator QVariant to be assignable to one.

    using QVariant = int; // to compile demo without Qt
    
    struct MyClass
    {
    public:
        MyClass(QVariant val) : m_value{ val } {}
        int value() const { return m_value; }
        void setValue(QVariant v) { m_value = v; }
    private:
        QVariant m_value;
    };
    
    class QVariantRef
    {
    public:
        QVariantRef(MyClass& ref) : ref{ &ref } {}
        QVariantRef& operator=(QVariant other)
        {
            ref->setValue(other);
            return *this;
        }
        QVariantRef& operator=(const QVariantRef& other)
        {
            // someObject[0] = someObject[1] works as expected
            ref->setValue(static_cast<QVariant>(other));
            return *this;
        }
        operator QVariant() const
        {
            return ref->value();
        }
    private:
        MyClass* ref;
    };
    
    int main()
    {
        MyClass c{ 5 };
        QVariantRef ref{ c };
        ref = 5; // assign to c
        QVariant val = ref; // copy c
    }
    

    this is done by std::vector<bool>, where it returns a std::vector<bool>::reference, which implements those two operations, because you cannot get a reference to a bit.

    this method has haters, because for example to iterate over a vector whose iterator returns proxies you'd need to use auto&& instead of auto& because you are getting back a proxy object, not an actual reference.

    for (auto&& val: vec)
    

    also this object is hard to use in template metaprogramming as it fails tests for reference like std::is_reference, for example the following next code doesn't work as expected

    MyClass c{ 5 };
    MyClass& ref {c}; 
    QVariantRef customRef{ c };
    auto val = ref; // val is copy of MyClass
    auto val2 = customRef; // val2 is QVariantRef not a copy of MyClass !
    

    also you cannot call its methods as a normal reference would allow. this was okay for bool because it had no methods, but for QVariant this will be a little bit confusing, will those methods work on the actual object or on a copy ? better not provide them.


    you'll also want to provide QVariantConstRef that has assignment operators deleted, so you can return it from operator[]() const


    Better Option

    Have const QVariant operator[]() const that returns a copy of the underlying variant (as const to disable assignment, to prevent misuse) and don't make all this proxy mess, and instead provide another function to set the variant that doesn't involve operator[] , it is more verbose but 10 times less confusing and safer.