Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 736
StoneThrow Avatar asked Aug 17 '16 06:08

StoneThrow


2 Answers

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])};
like image 119
Brian Bi Avatar answered Oct 19 '22 22:10

Brian Bi


If you look closer at the error message you will see that you are trying to construct the Bar object wtf with two functions. What's happening here is vexing, so vexing it's been officially named the most vexing parse.

What's happening is that you declare a and b as functions. Functions that take a pointer to std::string as argument and return a Foo object.

If you have a C++11 capable compiler you can use curly-braces instead of parentheses when constructing the Foo objects:

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

If you had an older compiler you can use copy-initialization instead:

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

It should also work fine if you don't explicitly create std::string objects for the constructor argument, and let the compiler handle that:

Foo a(argv[1]);
Foo b(argv[2]);
like image 10
Some programmer dude Avatar answered Oct 19 '22 23:10

Some programmer dude