24

In the following snippet, why does the line o.margin() = m; compile without fault? It easily deserves a warning, since it will almost invariably be a mistake. I would actually have thought it to be an error since it puts an R-Value on the left side of an assignment.

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

Output:

Margin is: 0
Margin is: 0
bgp2000
  • 1,070
  • 13
  • 32
  • "Why is it not a warning" Because it's not. What's your actual question? – uh oh somebody needs a pupper May 31 '16 at 11:54
  • @king_nak The language lawyer tag is really not necessary. This is a QOI issue, not a language issue. – uh oh somebody needs a pupper May 31 '16 at 11:57
  • I'm not familiar with the tag or QOI, but I meant it should be "at least a warning". I would have assumed it to be an error actually. – bgp2000 May 31 '16 at 12:05
  • QOI, I assume, means "quality of implementation." Which is exactly what this is about. Although the standard technically allows it (making [language-lawyer] inapplicable), there are vanishingly few reasons why you would want to write code like this, so a quality implementation arguably should issue a warning about it. Quite like how assignment is allowed in a conditional expression, but 99% of the time, this is not what you meant to do, so compilers now issue warnings when you write `if (foo = 42)` – Cody Gray - on strike Jun 01 '16 at 05:36

4 Answers4

14

You are allowed to modify return types of class type (by calling non const methods on it):

3.10/5 from n4140

5 An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [ Example: a member function called for an object (9.3) can modify the object. —end example ]

your code:

o.margin() = m;

is actually the same as

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

so non const method is called, if you change it to:

o.margin().val = m;

then you will get an error.

on the other hand here:

o.zoomLevel() = 2;

zoomLevel() returns non-class type, so you cannot modify it.

marcinj
  • 48,511
  • 9
  • 79
  • 100
  • 1
    I am accepting this an an answer since it references the standard. I am still surprised, though, that no major compiler issues a warning. – bgp2000 May 31 '16 at 12:36
  • @bgp2000 no warning is because standard allows it and there are idioms which uses this feature, for example: named-parameter-idiom : https://isocpp.org/wiki/faq/ctors#named-parameter-idiom – marcinj May 31 '16 at 12:50
  • 2
    I never doubted that one can call a member function on an rvalue. I just had never thought it would work with the assignment operator. Do you know of an idiom using the assignment operator? – bgp2000 May 31 '16 at 12:55
  • No, but its also a function – marcinj May 31 '16 at 13:03
10

When o is an object of class type, operator= is a member function. The code o.margin() = m; is equivalent to o.margin().operator=(m);.

You are allowed to call members functions of temporary class objects, similar to how you access a member in o.margin().val.

Also, the class' assignment operator can be overridden and not be a no-op at all.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
4

If you want to forbid such uses, since C++11 you can use a reference qualifier on the assignment operator:

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

This will generate the following error on GCC 5.1:

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

You might also want to check this related question.

Community
  • 1
  • 1
filipos
  • 645
  • 6
  • 12
  • 1
    That is interesting. The new c++ credo seems to be: If in doubt, add an ampersand. I am afraid that 99.9% of all c++ users won't know the meaning of that line. A -Warn-on-rvalue-assignment flag would be more useful. – bgp2000 May 31 '16 at 14:18
2

Option::margin() is a const-accessible member function that returns a mutable Margin object.

Consequently, assigning the temporary is valid because using operator= on Margin is valid. In this case it has no side-effects and basically does nothing. A particular implementation of a C++ compiler may choose to implement semantic analysis and warn you, but it's completely outside the scope of the language.

Olipro
  • 3,489
  • 19
  • 25