Const overload unexpectedly called in gcc. Compiler bug or compatibility fix?

When you return a plain id-expression from a function (that designated a function local object), the compiler is mandated to do overload resolution twice. First it treats it as though it was an rvalue, and not an lvalue. Only if the first overload resolution fails, will it be performed again with the object as an lvalue.

[class.copy.elision]

3 In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • If the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

  • ...

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided.  — end note ]

If we were to add an rvalue overload,

template <size_t N>
MyClass (char (&&value)[N])
{
    std::cout << "RVALUE " << value << '\n';
}

the output will become

RVALUE test_1
NON-CONST test_2
NON-CONST test_3
NON-CONST test_4
NON-CONST test_5

and this would be correct. What is not correct is GCC's behavior as you see it. It considers the first overload resolution a success. That is because a const lvalue reference may bind to an rvalue. However, it ignores the text "or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type". According to that it must discard the result of the first overload resolution, and do it again.

Well, that's the situation up to C++17 anyway. The current standard draft says something different.

If the first overload resolution fails or was not performed, overload resolution is performed again, considering the expression or operand as an lvalue.

The text from up to C++17 was removed. So it's a time traveling bug. GCC implement the C++20 behavior, but it does so even when the standard is C++17.