Search code examples
c++perfect-forwarding

Perfect forwarding for returned value?


I'm overloading an operator[]:

const Type&& operator[](int index) const {
        if (index >= size) { std::cout << "Error: excessive index.\n"; return 0; }
        else if (index < 0) { std::cout << "Error: negative index.\n"; return 0; }
        else {
            Node* temp = head->next;
            for (int i = 0; i < index; i++) { temp = temp->next; }
            return temp->value;
        }
    }

But I need a duplicate of it, which will return non-const Type value. I read that we can use perfect forwarding for cases when arguments of function can be both const or non-const (so that we wrap them in forward<Type> every time we use it), but how to use it for value that is returned?

Also, if I simply want to return nothing, should I write return 0; or return NULL;? Which is more understandable?


Solution

  • Such unified syntax that would work for all, const/volatile/non-const/lvalue/rvalue/etc., implicit object parameters is currently not supported. However, there's the proposal P0847r4: Deducing this which adds this functionality. With that, you could say:

    template <typename Self>
    auto&& operator[](this Self&& self, int index)
    {
        if (index >= self.size) { throw std::out_of_range("Error: excessive index"); }
        else if (index < 0) { throw std::out_of_range("Error: negative index"); }
    
        auto* temp = self.head;
        for (int i = 0; i < index; i++) { temp = temp->next; }        
        return std::forward_like<Self>(temp->value);
    }
    

    Until it becomes available, the best what you can do is to shorten the implementation for const and non-const overloads, and delegate both calls to a static helper function template, that actually can deduce the cv-qualification and value category of the implicit object parameter:

    class List
    {
    private:
        template <typename Self>
        static auto&& get(Self&& self, int index)
        {    
            if (index >= self.size) { throw std::out_of_range("Error: excessive index"); }
            else if (index < 0) { throw std::out_of_range("Error: negative index"); }
    
            Node* temp = self.head;
            for (int i = 0; i < index; i++) { temp = temp->next; }
            return temp->value;
        }
    
    public:
        const Type& operator[](int index) const
        {
            return get(*this, index);
        }
    
        Type& operator[](int index)
        {
            return get(*this, index);
        }
    
    private:
        // ...
    };
    

    DEMO

    Also, note that the idiomatic approach for a function returning a reference is to throw an exception in case nothing can be returned, or to insert ad-hoc and return a new object.