Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual C++: forward an array as a pointer

I've cut down some C++ 11 code that was failing to compile on Visual Studio 2015 to the following which I think should compile (and does with clang and gcc):

#include <utility>

void test(const char* x);

int main()
{
    const char x[] = "Hello world!";

    test(std::forward<const char*>(x));    
}

I understand the call to forward isn't necessary here. This is cut down from a much more complex bit of code that decays any arrays in a variadic argument down to pointers and forwards everything on. I'm sure can find ways to work around this with template specialization or SFINAE, but I'd like to know whether it's valid C++ before I go down that road. The compiler is Visual Studio 2015, and the problem can be recreated on this online MSVC compiler. The compile error is:

main.cpp(13): error C2665: 'std::forward': none of the 2 overloads could convert all the argument types
c:\tools_root\cl\inc\type_traits(1238): note: could be '_Ty &&std::forward<const char*>(const char *&&) noexcept'
        with
        [
            _Ty=const char *
        ]
c:\tools_root\cl\inc\type_traits(1231): note: or       '_Ty &&std::forward<const char*>(const char *&) noexcept'
        with
        [
            _Ty=const char *
        ]
main.cpp(13): note: while trying to match the argument list '(const char [13])'

Update:

@Yakk has suggested an example more like this:

void test(const char*&& x);

int main()
{
    const char x[] = "Hello world!";

    test(x);    
}

Which gives a more informative error:

main.cpp(7): error C2664: 'void test(const char *&&)': cannot convert argument 1 from 'const char [13]' to 'const char *&&'
main.cpp(7): note: You cannot bind an lvalue to an rvalue reference

Again, this compiles on gcc and clang. The compiler flags for Visual C++ were /EHsc /nologo /W4 /c. @Crazy Eddie suggests this might be down to a VC++ extension to pass temporaries as non const references.

like image 746
Simon Bourne Avatar asked Nov 17 '15 17:11

Simon Bourne


1 Answers

To me this looks like a bug in MSVC where it tries to be clever with array-to-pointer and gets it wrong.

Breaking down your second example:

The compiler needs to initialize a const char*&& from an lvalue of type const char[13]. To do this, 8.5.3 says it creates a temporary of type const char* and initializes it with the const char[13], then binds the reference to the temporary.

Initializing a const char* from a const char[13] involves a simple array-to-pointer conversion, yielding a prvalue of const char* which is then copied into the temporary.

Thus the conversion is well defined, despite what MSVC says.

In your first example, it's not test() that is causing the issue, but the call to std::forward. std::forward<const char*> has two overloads, and MSVC is complaining neither is viable. The two forms are

const char*&& std::forward(const char*&&);
const char*&& std::forward(const char*&);

One takes an lvalue reference, one takes an rvalue reference. When considering whether either overload is viable, the compiler needs to find a conversion sequence from const char[13] to a reference to const char*.

Since the lvalue reference isn't const (it's a reference to a pointer to a const char; the pointer itself isn't const), the compiler can't apply the conversion sequence outlined above. In fact, no conversion sequence is valid, as the array-to-pointer conversion requires a temporary but you can't bind non-const lvalue references to temporaries. Thus MSVC is correct in rejecting the lvalue form.

The rvalue form, however, as I've established above, should be accepted but is incorrectly rejected by MSVC.

like image 94
Falias Avatar answered Oct 14 '22 01:10

Falias