Search code examples
c++c++11rvalue-referenceforwarding-reference

Why call to overloaded overridden function with universal or r-value reference is ambiguous?


I have a problem related to reference forwarding, to which I am only slightly familiar with. Before stating my question, I want to say that I've read a few pages related to this language feature and they kind of confused me rather than shedding light into my problem. The consulted references are: [1-5], some of which I find contradictory or don't fully understand. Aware of the fact that this question could be considered a duplicate, I am posting it anyways because I couldn't find a case that would combine these three factors:

  • Overloaded functions with r-value references.
  • Templated classes (not functions).
  • Override of overloaded functions where the parent class' function is called in the overrides.

Problem scenario

I have two classes that I use to represent a variable of a model. The generic one is MState<T> and I template it because I want it to wrap any possible type inside. It's more like a wrapper to a type that I have created for a purpose which is irrelevant to this question. Then, I have MOutput<T>, which essentially extends MState<T> and represents an externally visible variable of a user-defined model.

They both have a member function called setValue which allows to, well... set their value. I know it would be nices to implement operator semantics, but for now I'm dealing with this:

template <class T>
class MState
{
public:
    virtual void setValue(T value) {      // -- (A)
        m_state = value;
        // (... more things.)
    }
    virtual void setValue(T&& value) {    // -- (B)
        m_state = value;
        // (... more things.)
    }
    T getValue(void) { return m_state; }
protected:
    T m_state;
};

template <class T>
class MOutput : public MState<T>
{
public:
    void setValue(T value) override {                     // -- (C)
        MState<T>::setValue(std::forward<T>(value));
        // (... even more things.)
    }
    void setValue(T&& value) override {                   // -- (D)
        MState<T>::setValue(std::forward<T>(value));
        // (... even more things.)
    }
};

I think I need (A) to allow for l-values to be valid arguments in calls to MState::setValue. Actually, if I remove it the compiler complains that it cannot convert an l-value to an r-value reference (which I understood with [4].) However, I want to avoid unnecessary resource allocations and copies, especially when dealing with temporaries (e.g. ms.setValue(MyClass()).) Hence (B).

Likewise, (C) is necessary because I need MOutput's to do a few things more, in addition to the ones MState implements. For the same motivation above, I also want (D).

The use of std::forward<T>(value) in (C) and (D) is motivated by [3].

Question

When I try to compile my code, GCC complains about the overloaded function signature to be ambiguous in both classes and a number of cases. I think I don't understand why, or how to solve the issue. Is T&& here a universal reference? If so, shouldn't these accept any kind of argument type?

Use cases

GCC fails to compile the line MState<T>::setValue(std::forward<T>(value)); of both (C) and (D). If I comment them out, I get the same ambiguity error in these cases:

bool function(int a) { return a >= 1; }
MOutput<bool> vo0, vo1, vo2;
vo1.setValue(true);                                // Error.
vo2.setValue(false);                               // Error.
vo0.setValue(!(vo1.getValue() & vo2.getValue()));  // Error.

MOutput<int> vo3;
vo3.setValue(432);                                 // Error.

Consulted references

  1. https://www.codesynthesis.com/~boris/blog/2012/06/26/efficient-argument-passing-cxx11-part2/
  2. https://stackoverflow.com/a/31551595/1876268
  3. https://stackoverflow.com/a/3582313/1876268
  4. https://stackoverflow.com/a/40350289/1876268
  5. https://stackoverflow.com/a/5465371/1876268

Solution

  • I would simply have simple void setValue(const T& value) (even more when your getter does copy).

    But if you want both l-value reference, and r-value reference, it would be something like:

    template <class T>
    class MState
    {
    public:
        virtual void setValue(const T& value) {
            m_state = value;
            MoreStuff()
        }
        virtual void setValue(T&& value) {
            m_state = std::move(value);
            MoreStuff()
        }
        const T& getValue() const { return m_state; }
    private:
        void MoreStuff();
    protected:
        T m_state;
    };
    
    template <class T>
    class MOutput : public MState<T>
    {
    public:
        void setValue(const T& value) override {
            MState<T>::setValue(value);
            MoreThings();
        }
        void setValue(T&& value) override {
            MState<T>::setValue(std::move(value));
            MoreThings();
        }
    private:
        void MoreThings();
    };
    

    Is T&& here a universal reference?

    As state, in comment, no it isn't, T is fixed by the class.