15

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • 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 Aug 17 '16 at 06:11
  • @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 Aug 17 '16 at 06:20
  • 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. – Some programmer dude Aug 17 '16 at 06:20
  • @StoneThrow Ah right, missed the `const`. And yes, the answers are fascinating. I had no idea this was a thing! – Sebastian Lenartowicz Aug 17 '16 at 06:21
  • @JoachimPileborg True enough. Point taken; thank you. – StoneThrow Aug 17 '16 at 06:40
  • `clang++` gives better error messages. – Stig Hemmer Aug 17 '16 at 10:46
  • I don't think that explicit mention of `std::string` is required, as `argv[1]` alone should resolve to trigger the `char *` constructor. Brace/Uniform initialisation might still be needed, though, and is syntactically superior for all situations (...unless it causes ambiguity with an constructor taking `std::initializer_list`) – underscore_d Aug 17 '16 at 14:07

3 Answers3

28

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])};
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 9
    This right here is why I always default to brace initialization when possible. – Alyssa Haroldsen Aug 17 '16 at 06:18
  • 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 Aug 17 '16 at 06:31
  • 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 Aug 17 '16 at 06:35
  • 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 is no longer proud of SO Aug 17 '16 at 10:00
  • 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 supports Monica Aug 17 '16 at 12:37
  • 1
    @MartinBonner: inconvenience* ;) – Lightness Races in Orbit Aug 17 '16 at 15:45
  • Great answer - one niggle, though: "less common" compared to what exactly? This is textbook MVP. You're not considering the common mistake `Foo a()` to be the most vexing parse, are you? – Lightness Races in Orbit Aug 17 '16 at 15:46
  • @LightnessRacesinOrbit the usual examples don't contain an array. – Brian Bi Aug 17 '16 at 19:01
  • @Brian: Okay, yep, that's an added dimension :) – Lightness Races in Orbit Aug 17 '16 at 19:21
  • @LightnessRacesinOrbit: Yes, it was clearly a mistake in retrospect, but it's about 30 years too late to change it now. – Martin Bonner supports Monica Aug 17 '16 at 19:39
10

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]);
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 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 Aug 17 '16 at 06:47
  • 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 is no longer proud of SO Aug 17 '16 at 10:01
  • @Agnew I still don't get where the array comes from, since there is no `std::string[]` in the argument to purported-function `a`; there is `std::string(something[])` which seems quite different to me due to the extra parentheses and extra identifier. It seems to make more sense (hah!) that `a` would be seen as taking a function named `std::string` which takes an array/pointer as argument. But that doesn't fit with what clang (Apple LLVM version 7.0.2 (clang-700.1.81)) makes of it: `Foo (std::string (*))`, whatever /that/ is again. – Olaf Seibert Aug 18 '16 at 12:36
  • @OlafSeibert `argv[1]` (for example) is a pointer to `char`, i.e. `char*`. So the compiler sees `std::string(char*)` and due to the syntactic rules of the language in the context (you really have to read [the specification](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/n4594.pdf) for the details) thinks it is equal to `std::string*`. There's really not much more to say. If you want the details [read the specification](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/n4594.pdf). Read the actually error message. And trust us that this is what is happening. :) – Some programmer dude Aug 18 '16 at 12:47
4

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"));
MikeCAT
  • 73,922
  • 11
  • 45
  • 70
  • 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 Aug 17 '16 at 14:04