Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

We have a much larger application that relies on template overloading of char and const char arrays. In gcc 7.5, clang, and visual studio, the code below prints "NON-CONST" for all cases. However, for gcc 8.1 and later, the output is shown below:

#include <iostream>

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

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

MyClass test_1()
{
    char buf[30] = "test_1";
    return buf;
}

MyClass test_2()
{
    char buf[30] = "test_2";
    return {buf};
}

void test_3()
{
    char buf[30] = "test_3";
    MyClass x{buf};
}

void test_4()
{
    char buf[30] = "test_4";
    MyClass x(buf);
}

void test_5()
{
    char buf[30] = "test_5";
    MyClass x = buf;
}

int main()
{
    test_1();
    test_2();
    test_3();
    test_4();
    test_5();
}

The gcc 8 and 9 output (from godbolt) is:

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

This appears to me to be a compiler bug, but I guess it could be some other issue related to a language change. Does anybody know definitively?

like image 622
Rob L Avatar asked Jan 27 '20 20:01

Rob L


People also ask

How do I turn off GCC warnings?

To answer your question about disabling specific warnings in GCC, you can enable specific warnings in GCC with -Wxxxx and disable them with -Wno-xxxx. From the GCC Warning Options: You can request many specific warnings with options beginning -W , for example -Wimplicit to request warnings on implicit declarations.

How does GCC treat warning errors?

You can use the -Werror compiler flag to turn all or some warnings into errors. Show activity on this post. You can use -fdiagnostics-show-option to see the -W option that applies to a particular warning. Unfortunately, in this case there isn't any specific option that covers that warning.

How do I enable all warnings in GCC?

For GCC, copying from the full list of warnings provided by this tool for your compiler version appears to be the only way to ensure that all warnings are turned on, since (unlike Clang) GCC does not provide -Weverything . The tool appears to parse the actual c.

What is Wextra?

-Wextra , among other stuff implies -Wtype-limits : Warn if a comparison is always true or always false due to the limited range of the data type, but do not warn for constant expressions. For example, warn if an unsigned variable is compared against zero with '<' or '>='. This warning is also enabled by -Wextra.


1 Answers

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.

like image 61
StoryTeller - Unslander Monica Avatar answered Oct 05 '22 22:10

StoryTeller - Unslander Monica