Search code examples
c++c++20c++-concepts

Why my MaybeConst concept doesn't resolve properly in C++?


I have the following concept:

template<typename T, typename U>
concept MaybeConst = std::is_same_v<U, std::remove_cv_t<T>>;

And a use-case for it looks like this (it's and example you can actually try):

#include <type_traits>


template<typename T, typename U>
concept MaybeConst = std::is_same_v<U, std::remove_cv_t<T>>;


class String {};


class Value {
protected:
    double number = 0;
public:
    Value & operator=(const Value & value) {
        number = value.number;
        return *this;
    }
    Value & operator=(const wchar_t * value) {
        number = 0;
        return *this;
    }
    Value & operator=(MaybeConst<String> auto & value) {
        number = 0;
        return *this;
    }
};

String getString() {
    return {};
}

int main(int argc, char * argv[]) {
    Value someValue;

    someValue = getString();

    return 0;
}

In the end, the compiler doesn't even count operator=(MaybeConst<String> auto & value) as an option when matching definitions and arguments.

It just says it's ambiguous what to chose between operator=(const Value & value) and operator=(const wchar_t * value):

ambiguous overload for ‘operator=’ (operand types are ‘Value’ and ‘String’)
note: candidate: ‘Value& Value::operator=(const Value&)’
note: candidate: ‘Value& Value::operator=(const wchar_t*)’

The only compiler flag is -fconcepts.


Solution

  • You want to bind an rvalue to auto &. This will not work since the auto will never deduce to a const-qualified type for a non-const rvalue (auto & is meant for lvalues). You want to use a forwarding reference instead:

    template<typename T> requires MaybeConst<std::remove_reference_t<T>, String>
    Value & operator=(T && value) {
        number = 0;
        return *this;
    }
    
    // Or
    template<typename T, typename U>
    concept IsSameDecayed = std::is_same_v<U, std::remove_cvref_t<T>>;
    
    
    Value & operator=(IsSameDecayed<String> auto && value) {
        number = 0;
        return *this;
    }
    

    Or you can just have a two overloads:

    private:
    void assign_string(auto & value) {
        // decltype(value) is `const String&` or `String&`
    }
    
    public:
    Value & operator=(      String & value) { assign_string(value); return *this; } 
    Value & operator=(const String & value) { assign_string(value); return *this; }
    

    Where someValue = getString(); will pick the const String & overload