С++ компилирует ошибку, создавая объект с rvalue std::string

Я столкнулся с ошибкой компиляции, которую я даже не знаю, как описать! Это полностью меня озадачивает.

Ситуация:

Код пытается создать объект в стеке с rvalue 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
>

Вы не будете верить в то, что обходное решение/или, или, по крайней мере, я не знаю. Если я вызову substr (0) в моем rvalue std::string, компилятор умиротворен. Но я не понимаю, почему это изменило бы ситуацию. В конце концов...

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

... сам по-прежнему является rvalue. Я не понимаю, почему это отличается от точки зрения компилятора.

т.е. следующее изменение основного (...) допускает успех компиляции:

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

Пара дополнительных точек данных:

  • Компиляция без С++ 11 не имеет значения (я включаю только его, чтобы получить доступ к std:: stod (...), который помимо точки).
  • g++ (GCC) 5.4.0.
  • Среда компиляции - cygwin.
  • Я попытался модифицировать конструктор Bar, чтобы принять std::string (вместо std::string &) - это не влияет на ошибку компиляции.

Я умираю, чтобы узнать, что это за проблема. Это чувствует себя так из левого поля.

Спасибо за любую помощь.

Ответы

Ответ 1

Это менее распространенный пример наиболее неприятного разбора. Объявление

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

не вызывает конструктор Foo со строковым аргументом; вместо этого он объявляет a функцией, принимающей массив из 1 std::string (скорректированный на указатель на std::string) и возвращающий Foo. Поэтому в сообщении об ошибке упоминается тип Foo (&)(std::string*): тип, который компилятор считает a и b. ((&) в сообщении просто означает, что это lvalue.)

Добавление .substr(0) устраняет неоднозначность объявления таким образом, что оно не может быть проанализировано как объявление функции.

Инициализация скобок - более элегантный способ решения проблемы:

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

Ответ 2

Если вы посмотрите ближе к сообщению об ошибке, вы увидите, что вы пытаетесь построить объект Bar wtf с двумя функциями. То, что здесь происходит, вызывает раздражение, поэтому досадно было официально названо самым неприятным анализом.

Что происходит, так как вы объявляете a и b в качестве функций. Функции, которые принимают указатель на std::string в качестве аргумента и возвращают объект Foo.

Если у вас есть компилятор с поддержкой С++ 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]);

Ответ 3

Кажется, компилятор принимает участие

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

как функции declareions. Вместо этого попробуйте:

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

или уточнить, что должен быть вызван конструктор:

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