Why is std::sort complaining about a deleted copy ctor?

Viewed 175

Let's say we have a simple class that holds a std::string:

class StringWrapper {
public:
    const std::string s;

    StringWrapper(const std::string s)
        : s(s) {}

    // We want this to be moveable but not copyable
    ~StringWrapper() = default;
    StringWrapper(const StringWrapper&) = delete;
    StringWrapper& operator=(const StringWrapper&) = delete;
    StringWrapper(StringWrapper&&) noexcept = default;
    StringWrapper& operator=(StringWrapper&&) = default;
};

Here we have tried to make the class moveable, but not copyable. I understand that a const member variable normally prevents a default move ctor from being generated, but here we try to explicitly generate it - and this at least compiles.

Now we try to sort a std::vector<StringWrapper> (the compare function is not important here):

std::vector<StringWrapper> strings;
std::sort(strings.begin(), strings.end(), [](auto const& a, auto const& b) { return true; });

This fails to compile, but the error is very cryptic:

1>...\include\algorithm(7419,25): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted
1>...\include\algorithm(7541): message : see reference to function template instantiation '_BidIt std::_Insertion_sort_unchecked<_RanIt,_Pr>(const _BidIt,const _BidIt,_Pr)' being compiled
1>        with
1>        [
1>            _BidIt=StringWrapper *,
1>            _RanIt=StringWrapper *,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...\include\algorithm(7571): message : see reference to function template instantiation 'void std::_Sort_unchecked<StringWrapper*,_Fn>(_RanIt,_RanIt,int,_Pr)' being compiled
1>        with
1>        [
1>            _Fn=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>,
1>            _RanIt=StringWrapper *,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...src\example.cpp(488): message : see reference to function template instantiation 'void std::sort<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<_Ty>>>,myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>>(const _RanIt,const _RanIt,_Pr)' being compiled
1>        with
1>        [
1>            _Ty=StringWrapper,
1>            _RanIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<StringWrapper>>>,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...\include\algorithm(7422,28): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted
1>...\include\algorithm(7425,24): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted

Specifically, it suggests that std::sort is trying to reference the deleted copy ctor, but I don't know why that would be the case; I would expect std::sort to use only move operations.

Can anyone explain to me what is going on here?

1 Answers

This has already been covered in the comments, but it's worth noting for posterity, the use of the default keyword does not guarantee that the compiler is always going to attempt to create a default implementation of a special member. This is (as usual) well-explained in the section on move assignment operators on cppreference.

The implicitly-declared or defaulted move assignment operator for class T is defined as deleted if any of the following is true:

  • T has a non-static data member that is const.

Compilers will also complain about this case as they do here. For example clang:

warning: explicitly defaulted move assignment operator is implicitly deleted [-Wdefaulted-function-deleted]
    StringWrapper& operator=(StringWrapper&&) = default;
                   ^
note: move assignment operator of 'StringWrapper' is implicitly deleted because field 's' has no move assignment operator
    const std::string s;

Note that it's a warning and not an error, which is how you end up in this situation. In general it's best not to assume the default keyword allows you to bypass language restrictions.

Related