Search code examples
c++constructorstdstdstringmost-vexing-parse

C++ compile error constructing object with rvalue std::string


I'm faced with a compile error that I don't even know how to describe! It completely baffles me.

The situation:

Code tries to create an object on the stack with an rvalue std::string that is initialized with a char*.

The code:

#include <iostream>
#include <string>

class Foo
{
    public:
        Foo(double d)
            : mD(d)
        {
        }

        Foo(const std::string& str)
        {
            try
            {
                mD = std::stod(str);
            }
            catch (...)
            {
                throw;
            }
        }

        Foo(const Foo& other)
            : mD(other.mD)
        {
        }

        virtual ~Foo() {}

    protected:
        double mD;
};

class Bar
{
    public:
        Bar(const Foo& a, const Foo& b)
            : mA(a)
            , mB(b)
        {
        }

        virtual ~Bar() {}

    protected:
        Foo mA;
        Foo mB;
};

int main(int argc, char* argv[])
{
    if (argc < 3) { return 0; }

    Foo a(std::string(argv[1]));
    Foo b(std::string(argv[2]));

    Bar wtf(a, b);
}

The compiler error:

>g++ -std=c++11 wtf.cpp
wtf.cpp: In function ‘int main(int, char**)’:
wtf.cpp:58:17: error: no matching function for call to ‘Bar::Bar(Foo (&)(std::string*), Foo (&)(std::string*))’
     Bar wtf(a, b);
                 ^
wtf.cpp:38:9: note: candidate: Bar::Bar(const Foo&, const Foo&)
         Bar(const Foo& a, const Foo& b)
         ^
wtf.cpp:38:9: note:   no known conversion for argument 1 from ‘Foo(std::string*) {aka Foo(std::basic_string<char>*)}’ to ‘const Foo&’
wtf.cpp:35:7: note: candidate: Bar::Bar(const Bar&)
 class Bar
       ^
wtf.cpp:35:7: note:   candidate expects 1 argument, 2 provided
>

You won't believe what the/a workaround is, either (or at least I don't). If I call substr(0) on my rvalue std::string, the compiler is pacified. But I don't see why this would make a difference. After all...

std::string(argv[1]).substr(0)

...is itself still an rvalue. I don't see why it's different from the compiler's point of view.

I.e. the following change to main(...) allows compilation success:

int main(int argc, char* argv[])
{
    if (argc < 3) { return 0; }

    Foo a(std::string(argv[1]).substr(0));
    Foo b(std::string(argv[2]).substr(0));

    Bar wtf(a, b);
}

Couple of additional data points:

  • Compiling without C++11 makes no difference (I only include it to get access to std::stod(...), which is besides the point).
  • g++ (GCC) 5.4.0.
  • Compilation environment is cygwin.
  • I have tried modifying Bar's constructor to take std::string (instead of std::string&) - it does not affect the compile error.

I'm dying to know what this problem is. This feels so out of left field.

Thanks for any help.


Solution

  • This is a less common example of the most vexing parse. The declaration

    Foo a(std::string(argv[1]));
    

    is not calling the constructor of Foo with a string argument; instead, it is declaring a to be a function taking an array of 1 std::string (adjusted to a pointer to std::string) and returning Foo. That's why the error message is mentioning a Foo (&)(std::string*) type: that's the type the compiler thinks a and b are. (The (&) in the message just means that it's an lvalue.)

    Adding .substr(0) disambiguates the declaration so that it cannot be parsed as a function declaration.

    Brace initialization is a more elegant way to solve the problem:

    Foo a{std::string(argv[1])};