Подтвердить что ты не робот

Функция возвращает struct как LValue

В следующем фрагменте, почему строка o.margin() = m; скомпилирована без ошибок? Он легко заслуживает предупреждения, поскольку он почти всегда будет ошибкой. Я бы на самом деле подумал, что это ошибка, поскольку она помещает значение R в левую часть задания.

#include <iostream>

struct Margin
{
    Margin(int val=0) : val(val) {};
    int val;
};

struct Option
{
    Margin m;
    int z=0;

    Margin margin()const { return m; }
    int zoomLevel() { return z; }
};


int main()
{
    Option o;
    std::cout << "Margin is: "<< o.margin().val << std::endl;

    Margin m = { 3 };

    // The following line is a no-op, which generates no warning:
    o.margin() = m;

    // The following line is an error
    // GCC 4.9.0: error: lvalue required as left operand of assignment
    // clang 3.8: error: expression is not assignable
    // MSVC 2015: error C2106: '=': left operand must be l-value
     o.zoomLevel() = 2;

    std::cout << "Margin is: "<< o.margin().val << std::endl;

    return 0;
}

Вывод:

Margin is: 0
Margin is: 0
4b9b3361

Ответ 1

Вам разрешено изменять типы возвращаемого типа типа (вызывая на нем методы non const):

3.10/5 из n4140

5 Для изменения объекта требуется lvalue для объекта за исключением того, что rvalue типа класса также может использоваться для изменения его референт при определенных обстоятельствах. [Пример: функция-член вызываемый для объекта (9.3), может изменять объект. -end пример]

ваш код:

o.margin() = m;

на самом деле совпадает с

o.margin().operator=( Margin(m) );

поэтому вызывается метод non const, если вы меняете его на:

o.margin().val = m;

то вы получите сообщение об ошибке.

с другой стороны:

o.zoomLevel() = 2;

zoomLevel() возвращает тип неклассов, поэтому вы не можете его изменять.

Ответ 2

Когда o является объектом типа класса, operator= является функцией-членом. Код o.margin() = m; эквивалентен o.margin().operator=(m);.

Вам разрешено вызывать функции членов временных объектов класса, аналогично тому, как вы обращаетесь к члену в o.margin().val.

Кроме того, оператор присваивания класса может быть переопределен и вообще не будет отсутствовать.

Ответ 3

Если вы хотите запретить такое использование, так как С++ 11 вы можете использовать спецификатор ссылок для оператора присваивания:

Margin& operator=(const Margin&) & = default;

Это приведет к появлению следующей ошибки в GCC 5.1:

error: passing 'Margin' as 'this' argument discards qualifiers [-fpermissive]

Вы также можете проверить этот связанный вопрос.

Ответ 4

Option::margin() - это константная функция-член, которая возвращает изменяемый объект Margin.

Следовательно, назначение временного действительно, потому что используется operator= on Margin. В этом случае он не имеет побочных эффектов и в основном ничего не делает. Конкретная реализация компилятора С++ может реализовать семантический анализ и предупредить вас, но полностью выходит за рамки языка.