【问题标题】:C++ compile error constructing object with rvalue std::stringC++ 编译错误用右值 std::string 构造对象
【发布时间】:2016-12-23 16:09:44
【问题描述】:

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

情况

代码尝试在堆栈上创建一个带有右值 std::string 的对象,该右值用 char* 初始化。

代码

#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);
}

编译器错误

>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
>

您也不会相信/a 解决方法是什么(或者至少不相信)。如果我在我的右值 std::string 上调用 substr(0),编译器就会被安抚。但我不明白为什么这会有所作为。毕竟……

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

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

即对 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);
}

几个额外的数据点:

  • 在不使用 C++11 的情况下进行编译没有区别(我只包含它以访问 std::stod(...),这不是重点)。
  • g++ (GCC) 5.4.0.
  • 编译环境为cygwin。
  • 我已尝试修改 Bar 的构造函数以采用 std::string(而不是 std::string&) - 它不会影响编译错误。

渴望知道这个问题是什么。这感觉太离谱了。

感谢您的帮助。

【问题讨论】:

  • 在这种情况下将std::string&amp; 用于Foo 的构造函数显然是错误的——它会引用一个临时对象,这充其量是有问题的(尽管我不记得了如果它是非法的或只是未定义)。
  • @SebastianLenartowicz 仅供参考,这是合法的,因为我使用的是 const ref。不同于非常量参考。查看答案 RE:最令人烦恼的解析;太迷人了!
  • 与您的问题无关,但 stod 调用的 try-catch,如果您所做的只是重新抛出异常,则无需 try-catch。
  • @StoneThrow 对,错过了const。是的,答案很有趣。我不知道这是一件事!
  • @JoachimPileborg 确实如此。取点;谢谢。

标签: c++ constructor std stdstring most-vexing-parse


【解决方案1】:

这是most vexing parse 的一个不太常见的示例。声明

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

没有使用字符串参数调用Foo 的构造函数;相反,它将a 声明为一个函数,该函数采用1 个数组std::string(调整为指向std::string 的指针)并返回Foo。这就是错误消息提到Foo (&amp;)(std::string*) 类型的原因:这是编译器认为ab 的类型。 (消息中的(&amp;) 仅表示它是一个左值。)

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

大括号初始化是一种更优雅的解决问题的方法:

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

【讨论】:

  • 这就是为什么我总是尽可能默认使用大括号初始化。
  • 这很吸引人;谢谢。我想我有点不清楚为什么编译器需要考虑这可能是一个函数定义,因为它在另一个函数的范围内(即 main(...))。就此而言,还有为什么会对指向 std::string 的指针进行隐式调整。不过,我将尝试让自己脱离大括号初始化。再次感谢你。这确实令人烦恼!
  • @StoneThrow 函数声明可能出现在函数内部。 (IMO 语言功能很差,但出于历史兼容性无法删除)
  • @StoneThrow 编译器不认为它是一个函数 definition (这确实是非法的)。它认为它是一个函数声明,,它可以出现在其他函数中就好了。
  • @StoneThrow:“隐式调整”。函数不能将数组作为参数(尽管 C++ 函数可以将 references 带到数组)。为了方便用户,C 和 C++ 都允许您将函数声明为采用数组,但参数被隐式调整以实际成为指针。所以void foo(int a[1]) 变成了void foo(int *a)
【解决方案2】:

如果您仔细查看错误消息,您会发现您正在尝试使用两个函数构造Bar 对象wtf。这里发生的事情很烦人,所以很烦人,它被正式命名为the most vexing parse

发生的情况是您将ab 声明为函数。将指向 std::string 的指针作为参数并返回 Foo 对象的函数。

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

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

如果您的编译器较旧,则可以改用复制初始化:

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

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

Foo a(argv[1]);
Foo b(argv[2]);

【讨论】:

  • 如果你仍然在这个线程上活跃:我不确定我是否理解为什么 a(和 b)可以被解释为将 pointers 指向@的函数987654335@。您所说的由编译器错误证实,但是编译器如何证明从对象到指针的隐式转换是正确的?根据我的阅读,“最令人烦恼的解析”链接并没有完全回答这个问题。
  • @StoneThrow 声明一个函数以按值获取数组(例如 int foo(int x[3]) 只是获取指针的替代语法 (int foo(int *x))。在 C 和 C++ 中一直都是这种情况.IOW,编译器认为你的函数有一个名为argv的参数,它是std::string的1大小数组。对于函数参数,它与指向std::string的指针相同。
  • @Agnew 我仍然不知道数组的来源,因为在 purported-function a 的参数中没有 std::string[];有 std::string(something[]) 由于额外的括号和额外的标识符,这对我来说似乎完全不同。似乎更有意义(哈!)a 将被视为采用名为 std::string 的函数,该函数将数组/指针作为参数。但这不符合 clang(Apple LLVM 版本 7.0.2 (clang-700.1.81))的含义:Foo (std::string (*)),不管 /that/ 是什么。
  • @OlafSeibert argv[1](例如)是指向char 的指针,即char*。所以编译器看到std::string(char*),并且由于上下文中语言的句法规则(你真的必须阅读the specification了解细节)认为它等于std::string*。真的没什么好说的了。如果您想了解详细信息read the specification。阅读实际的错误消息。相信我们,这就是正在发生的事情。 :)
【解决方案3】:

编译器似乎参与其中

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

作为函数声明。试试这个:

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

或澄清应该调用构造函数:

Foo a = Foo(std::string("1"));
Foo b = Foo(std::string("2"));

【讨论】:

  • ...或者只使用统一初始化,因为绕过诸如最令人烦恼的解析之类的事情是其主要理由/成功之一! Foo a{std::string(argv[1])}.
猜你喜欢
  • 2012-06-05
  • 1970-01-01
  • 2011-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多