10

Can anybody explain why this code compiles:

typedef struct longlong
{
  unsigned long low;
  long high;
}
longlong;

typedef longlong Foo;    

struct FooStruct
{
private:
  Foo bar;

public:
  void SetBar(Foo m)
  {
    bar = m;
  }

  Foo GetBar()
  {
    return bar;
  }
};

int main()
{
  FooStruct f;
  Foo m1 = { 1,1 };
  Foo m2 = { 2,2 };
  f.SetBar(m1);
  f.GetBar() = m2;     // Here I'd expect an error such as
                       // "error: lvalue required as left operand of assignment"
}

I expected the compilation to fail with error: lvalue required as left operand of assignment on line f.GetBar() = m2; because IMO f.GetBar() is not an l-value, but it compiles seemlessly and f.GetBar() = m2; is a NOP.

On the other hand if I replace the typedef longlong Foo; by typedef long Foo;, the line mentioned before won't compile and I get the expected error.

I came along this issue while refactoring some old code. The code in this question has no purpose other than to illustrate this issue.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    I was curious whether this issue exists in Rust, so I wrote up a [quick example](https://github.com/BatmanAoD/rust-sandbox/blob/master/retval_assign.rs). It does not; the example fails to compile, stating that the assignment is improper. – Kyle Strand May 30 '17 at 16:49
  • 1
    This issue also [does not affect C](http://rextester.com/MSCPGT70980), so it is in fact an incompatibility in the C-subset of C++ (!!). (In the linked example, switching from C to C++ prevents an error from being emitted.) – Kyle Strand May 31 '17 at 23:16

4 Answers4

12

This works because longlong is a class, and as such = is longlong::operator =, the implicitly-defined assignment operator. And you can call member functions on temporaries as long as they're not qualified with & (the default operator = is unqualified).

To restrict the assignment operator to lvalues, you can explicitly default it with an additional ref-qualifier:

struct longlong
{
    longlong &operator = (longlong const &) & = default;
    //                                      ^

    // ...
};
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • This sounds convincing. But what can I do so `f.GetBar() = m2;` won't compile. I'd need that for my refactoring process. – Jabberwocky May 30 '17 at 12:38
  • 1
    `delete` the assignment operator? – Bathsheba May 30 '17 at 12:38
  • 1
    Qualify `operator=` with `&`. As stated in ^. – Hatted Rooster May 30 '17 at 12:39
  • @GillBates: Yes that would be better than my sledgehammer idea. – Bathsheba May 30 '17 at 12:39
  • @MichaelWalz `Foo &operator = (Foo const &) & = default;`, note the `&` :) – Quentin May 30 '17 at 12:41
  • 1
    @Quentin What does the `&` after parameter mean? – VINOTH ENERGETIC May 30 '17 at 12:44
  • @Quentin unfortunately, `= default` cannot be used on ref-qualified functions – M.M May 30 '17 at 12:46
  • @VINOTHENERGETIC the parameter? That's your plain old reference-to-const, from the usual copy-assignment signature. – Quentin May 30 '17 at 12:46
  • 2
    @VINOTHENERGETIC It means that that method is only used if `this` is `&`. https://stackoverflow.com/questions/28066777/const-and-specifiers-for-member-functions-in-c – Hatted Rooster May 30 '17 at 12:47
  • @M.M mmh, [works fine here](http://coliru.stacked-crooked.com/a/a09a75ff1964d6d3), `-pedantic-error` and all. Would that be a compiler bug? – Quentin May 30 '17 at 12:47
  • 1
    @Quentin Hmm.. I tried on several compilers (incl gcc 7.1 and 8) and it told me I couldn't, but clearly I did not try the version you used there :) I guess someone will have to trawl the standard to resolve this – M.M May 30 '17 at 12:50
  • 4
    @M.M got it! [dcl.fct.def.default](http://eel.is/c++draft/dcl.fct.def.default): "A function that is explicitly defaulted shall [...] have the same declared function type (**except for possibly differing ref-qualifiers** [...]) as if it had been implicitly declared[.]". So this is fine :) – Quentin May 30 '17 at 13:00
  • @anatolyg thanks, I was just checking that it is actually standard before lifting it in :) – Quentin May 30 '17 at 13:16
  • 1
    @Quentin Good job. Looks like that's been in there for a while so it seems to be a long-standing bug that gcc doesn't accept it – M.M May 30 '17 at 21:14
  • I suppose the move-assignment operator should also get the same treatment – M.M May 30 '17 at 21:16
5

Using operators on objects of class type means to call a function (either a member function of the left-hand operand, or a free function taking the left-hand operand as first argument). This is known as operator overloading.

It's fine to call functions on rvalues so there is no compilation error.

When implementing the overloaded assignment operator, you can mark it so that it can not be called on an rvalue, but the designer of whatever class you are using chose not to do that.

M.M
  • 138,810
  • 21
  • 208
  • 365
5
  f.GetBar() = m2;     // Here I'd expect an error such as
                       // "error: lvalue required as left operand of assignment"

Your expectation is in error. This rule only applies to objects of built-in type.

[C++14: 3.10/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 ]

Recall that your = here is actually a call to operator=, which is a function.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
4

Reasons for this behavior were described in other answers.

One way to avoid this behavior would be qualifying your return type with const:

struct FooStruct
{
    ...
    const Foo GetBar() {return bar;}
};

You cannot assign values to const objects, so the compiler will complain.

anatolyg
  • 26,506
  • 9
  • 60
  • 134