C++使用rvalue std :: string编译错误构造对象

Sto*_*row 15 c++ constructor std stdstring most-vexing-parse

我遇到了一个我甚至不知道如何描述的编译错误!这完全让我感到困惑.

情况:

代码尝试使用用char*初始化的rvalue std :: string在堆栈上创建一个对象.

代码:

#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);
}
Run Code Online (Sandbox Code Playgroud)

编译错误:

>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
>
Run Code Online (Sandbox Code Playgroud)

你不会相信/一个变通方法是什么(或者至少我没有).如果我在我的rvalue std :: string上调用substr(0),则编译器会被平息.但我不明白为什么这会有所作为.毕竟...

std::string(argv[1]).substr(0)
Run Code Online (Sandbox Code Playgroud)

......本身仍然是一个价值.我不明白为什么它与编译器的观点不同.

即对main(...)的以下更改允许编译成功:

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);
}
Run Code Online (Sandbox Code Playgroud)

几个额外的数据点:

  • 没有C++ 11的编译没有区别(我只包括它来访问std :: stod(...),除了这一点).
  • g ++(GCC)5.4.0.
  • 编译环境是cygwin.
  • 我尝试修改Bar的构造函数以获取std :: string(而不是std :: string&) - 它不会影响编译错误.

我死去知道这是什么问题.左边的场地让人感觉如此.

谢谢你的帮助.

Bri*_*ian 28

这是最令人烦恼的解析的一个不太常见的例子.声明

Foo a(std::string(argv[1]));
Run Code Online (Sandbox Code Playgroud)

不是Foo用字符串参数调用构造函数; 相反,它声明a是一个函数,取一个数组1 std::string(调整到指针std::string)并返回Foo.这就是为什么错误消息提一个Foo (&)(std::string*)类型:这是编译器认为该类型a和b是.((&)消息中的含义仅表示它是左值.)

添加.substr(0)消除歧义的歧义,以便它不能被解析为函数声明.

Brace初始化是解决问题的更优雅的方法:

Foo a{std::string(argv[1])};
Run Code Online (Sandbox Code Playgroud)

  • 这就是为什么我总是默认支持初始化的可能性. (9认同)
  • @StoneThrow函数声明可能出现在函数内部.(一种可怜的语言功能IMO,但由于历史兼容性而无法删除) (4认同)
  • @StoneThrow编译器不认为它是一个函数*定义*(这确实是非法的).它正在考虑它是一个函数*声明,*虽然,它可以出现在其他函数中就好了. (2认同)
  • @StoneThrow:"隐式调整".函数不能将数组作为参数(尽管C++函数可以将*引用*作为数组).为方便用户,C和C++都允许您将函数声明为接受数组,但参数*被隐式调整*实际上是指针.所以`void foo(int a [1])`变成`void foo(int*a)`. (2认同)

Som*_*ude 10

如果仔细观察错误消息,您将看到正在尝试使用两个函数构造Bar对象.这里发生的事情令人烦恼,令人烦恼,它被正式命名为最令人烦恼的解析.wtf

发生的事情是你宣布a和b作为功能.将指针std::string作为参数并返回Foo对象的函数.

如果你有一个支持C++ 11的编译器,你可以在构造Foo对象时使用大括号而不是括号:

Foo a{std::string(argv[1])};
Foo b{std::string(argv[2])};
Run Code Online (Sandbox Code Playgroud)

如果你有一个较旧的编译器,你可以使用复制初始化:

Foo a = Foo(std::string(argv[1]));
Foo b = Foo(std::string(argv[2]));
Run Code Online (Sandbox Code Playgroud)

如果没有std::string为构造函数参数显式创建对象,它应该也可以正常工作,让编译器处理:

Foo a(argv[1]);
Foo b(argv[2]);
Run Code Online (Sandbox Code Playgroud)

  • @StoneThrow声明一个按值获取数组的函数(例如`int foo(int x [3])`只是取指针的替代语法(`int foo(int*x)`).这一直是C语言和C++中的情况.IOW,编译器认为你的函数有一个名为`argv`的参数,它是一个1大小的`std :: string`数组.对于一个函数参数,它与指向`std的指针相同:: string`. (4认同)
  • 代码提到了`std :: stod`,所以它是> = C++ 11. (3认同)