Avoiding self assignment in std::shuffle

The libstdc++ Debug Mode assertion is based on this rule in the standard, from [res.on.arguments]

If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument.

i.e. the implementation can assume that the object bound to the parameter of T::operator=(T&&) does not alias *this, and if the program violates that assumption the behaviour is undefined. So if the Debug Mode detects that in fact the rvalue reference is bound to *this it has detected undefined behaviour and so can abort.

The paragraph contains this note as well (emphasis mine):

[Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g., by calling the function with the argument
std::move(x)), the program is effectively asking that function to treat that lvalue as a temporary object. The implementation is free to optimize away aliasing checks which might be needed if the
argument was an lvalue.
—end note]

i.e. if you say x = std::move(x) then the implementation can optimize away any check for aliasing such as:

X::operator=(X&& rval) { if (&rval != this) ...

Since the implementation can optimize that check away, the standard library types don’t even bother doing such a check in the first place. They just assume self-move-assignment is undefined.

However, because self-move-assignment can arise in quite innocent code (possibly even outside the user’s control, because the std::lib performs a self-swap) the standard was changed by Defect Report 2468. I don’t think the resolution of that DR actually helps though. It doesn’t change anything in [res.on.arguments], which means it is still undefined behaviour to perform a self-move-assignment, at least until issue 2839 gets resolved. It is clear that the C++ standard committee think self-move-assignment should not result in undefined behaviour (even if they’ve failed to actually say that in the standard so far) and so it’s a libstdc++ bug that our Debug Mode still contains assertions to prevent self-move-assignment.

Until we remove the overeager checks from libstdc++ you can disable that individual assertion (but still keep all the other Debug Mode checks) by doing this before including any other headers:

#include <debug/macros.h>
#undef __glibcxx_check_self_move_assign
#define __glibcxx_check_self_move_assign(x)

Or equivalently, using just command-line flags (so no need to change the source code):

-D_GLIBCXX_DEBUG -include debug/macros.h -U__glibcxx_check_self_move_assign '-D__glibcxx_check_self_move_assign(x)='

This tells the compiler to include <debug/macros.h> at the start of the file, then undefines the macro that performs the self-move-assign assertion, and then redefines it to be empty.

(In general defining, undefining or redefining libstdc++’s internal macros is undefined and unsupported, but this will work, and has my blessing).

Leave a Comment