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.

share|improve this question
    
Taking std::string& for Foo's constructor in this case would be patently wrong - it would be taking a reference to a temporary object, which is problematic at best (though I can't remember if it's illegal or just undefined). – Sebastian Lenartowicz 23 hours ago
    
@SebastianLenartowicz Just fyi, it is legal because I'm using a const ref. Different than a non-const ref. See the answers RE: most vexing parse; it's fascinating! – StoneThrow 23 hours ago
1  
Unrelated to your problem, but that try-catch for the stod call, if all you are doing is rethrowing the exception then there's no need for a try-catch. – Joachim Pileborg 23 hours ago
    
@StoneThrow Ah right, missed the const. And yes, the answers are fascinating. I had no idea this was a thing! – Sebastian Lenartowicz 23 hours ago
    
@JoachimPileborg True enough. Point taken; thank you. – StoneThrow 22 hours ago
up vote 25 down vote accepted

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])};
share|improve this answer
9  
This right here is why I always default to brace initialization when possible. – Kupiakos 23 hours ago
1  
This is fascinating; thank you. I guess I'm a bit unclear why the compiler needs to consider this possibly a function definition since it's within the scope of another function (i.e. main(...)). For that matter, also why there would be an implicit adjustment to a pointer to std::string. I'm going to try to wean myself onto brace initialization, though. Thank you again. This was vexing, indeed! – StoneThrow 23 hours ago
4  
@StoneThrow function declarations may appear inside functions . (A poor language feature IMO, but one that can't be removed for historical compatibility) – M.M 23 hours ago
2  
@StoneThrow The compiler is not considering it a function definition (that would indeed be illegal). It is considering it a function declaration, though, which can appear in other functions just fine. – Angew 19 hours ago
2  
@StoneThrow: "implicit adjustment". Function cannot take arrays as arguments (although C++ functions can take references to arrays). As a convenience to the user, both C and C++ allow you to declare a function as taking an array, but the argument is implicitly adjusted to actually be a pointer. So void foo(int a[1]) becomes void foo(int *a). – Martin Bonner 16 hours ago

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]);
share|improve this answer
2  
The code mentions std::stod, so it is >= C++11. – Brian 23 hours ago
    
In case you're still active on this thread: I'm not sure I understand yet why a (and b) could be interpreted as functions that take pointers to std::string. What you say is borne out by the compiler error, but how could the compiler justify an implicit conversion from an object to a pointer? The "most vexing parse" link didn't quite answer this per my reading. – StoneThrow 22 hours ago
4  
@StoneThrow Declaring a function to take an array by value (such as int foo(int x[3]) is just alternate syntax for taking a pointer (int foo(int *x)). This has always been the case in both C and C++. IOW, the compiler thinks your function has a parameter named argv which is a 1-sized array of std::string. For a function parameter, that's the same as a pointer to std::string. – Angew 19 hours ago

It seems compiler takes the part

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

as function declaretions. Try this instead:

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

or to clarify that constructor should be called:

Foo a = Foo(std::string("1"));
Foo b = Foo(std::string("2"));
share|improve this answer
1  
...or just use uniform initialisation, since bypassing things like the most vexing parse is one of its main rationales/successes! Foo a{std::string(argv[1])}. – underscore_d 15 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.