8

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.

Simon Bourne
  • 455
  • 3
  • 13
  • I would expect it not to match the second version because the pointer is actually a temporary. – Edward Strange Nov 17 '15 at 18:07
  • 3
    Is your compiler in strict C++ mode, or does it have *any* extensions enabled? – Yakk - Adam Nevraumont Nov 17 '15 at 18:13
  • A simpler example which shows the difference may be `void test(const char*&&){} const char bob[]="hello"; test(bob);`? – Yakk - Adam Nevraumont Nov 17 '15 at 18:16
  • Note that `test` (definition and call) are not part of a minimal example. – Ben Voigt Nov 17 '15 at 18:23
  • I think @Yakk may be onto your problem. MSVC++ has that passing temporary as non-const reference extension. – Edward Strange Nov 17 '15 at 18:36
  • I think you might be right. The compiler flags on the online compiler are "/EHsc /nologo /W4 /c". I'll update the question to include a more minimal example using std::forward and add another example with the @Yakk suggestion about bypassing std::forward altogether, as that gives a clearer error message. – Simon Bourne Nov 17 '15 at 18:45
  • @ben my test reolaces forward, emulates one of the two overload failures. That error happens only in msvc, not in gcc. – Yakk - Adam Nevraumont Nov 17 '15 at 18:45
  • @SimonBourne "Turn off extensions" doesn't change it. But it might still be an extension that does not turn off. Either that, a bug, or an ambiguity in the standard. Note that `test(const char*&)` won't bind to the array *either*. – Yakk - Adam Nevraumont Nov 17 '15 at 19:09
  • @Yakk, you're right, I tried compiling with "/Za" and "/Zc:rvalueCast" for good measure, and got the same results. I'm probably going to have to work around the problem anyway as it's library code and requiring users to use various compile flags is not ideal. – Simon Bourne Nov 17 '15 at 19:13

3 Answers3

4

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.

Falias
  • 636
  • 4
  • 7
  • Thanks. I'll wait and see if you get time to dig into your last point, and look at filing a bug report. – Simon Bourne Nov 17 '15 at 20:41
  • I misinterpreted the output for the first one; I thought it was saying it was ambiguous, but it actually was rejecting both outright. I've updated explaining the issue. – Falias Nov 17 '15 at 20:49
  • But the temporary pointer from the array decay isn't itself const, so shouldn't it match the (non const) rvalue ref overload of forward? – Simon Bourne Nov 17 '15 at 21:06
  • i.e. The decltype of the decayed type is const char*&& – Simon Bourne Nov 17 '15 at 21:18
  • That's the point, a compliant compiler would match the rvalue ref overload, but MSVC doesn't – Falias Nov 17 '15 at 21:21
  • My bad. I read "Thus MSVC is correct in rejecting the lvalue form" as "Thus MSVC is correct in rejecting the 2 overloads", which is not what you said at all. I'll file a bug report about the simpler case tomorrow as the std::forward case just looks like a knock on effect. – Simon Bourne Nov 17 '15 at 21:30
  • @SimonBourne Looks like this [has been reported](https://connect.microsoft.com/VisualStudio/feedback/details/1037806/implicit-conversion-doesnt-perform-for-fund) before. – bogdan Nov 17 '15 at 23:22
0

I believe std::decay<const char []>::type is what you're looking for http://en.cppreference.com/w/cpp/types/decay

Nick Strupat
  • 4,928
  • 4
  • 44
  • 56
  • std::decay is what's causing the problem. I want to decay the array to a pointer, but when I do I get this problem. In the sample code I've manually done the array part of the decay (const char [N] -> const char*) on the template parameter to forward to make the code simpler and more specific to the problem. – Simon Bourne Nov 17 '15 at 18:24
-1

I think it should compile, but why are you bothering to use std::forward?

Isn't the correct solution simply to replace

std::forward<const char*>(x)

with:

(const char*)x

or for the generic case, replace:

std::forward<decay_t<decltype(x)>>(x)

with:

decay_t<decltype(x)>(x)

Using std::forward doesn't seem to have any purpose here, you have an array, you want to decay it to a pointer, so do that.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I agree, in isolation, it's not what you'd write. It's a cut down version so I can ask a specific question. For example, the original code is forwarding the elements of a parameter pack after decaying any arrays (it does mention this in the question, but maybe it's not that clear). So some of the elements may be arrays and some may not, hence the need for decay. Some may be rvalue refs (perhaps not copyable), some may be lvalues, hence the need for forwarding. – Simon Bourne Nov 17 '15 at 20:35