33

Basically the example says it all: assigning to a std::chrono literal - does not give an error message. I tried on gcc, clang and vc, (c++17) all these compilers accept it. Is this intended behaviour?

#include <chrono>
using namespace std::chrono_literals;

int main()
{
    10ms  += 1ms;       // would expect error here
    12ms = 10ms;        // and here
    // 3 = 4;           // like here
    return 0;
}
DanRechtsaf
  • 518
  • 3
  • 8

2 Answers2

25

are std::chrono literals lvalues?

No. std::chrono_literals are prvalues.

Is this intended behaviour?

It is as the standard has specified.

These values are of class types. And rvalues of class types can be assigned1 as long as the class is assignable, and their assignment operator overload is not lvalue ref qualified. Assignment operators of classes, like all member functions, are not lvalue ref qualified by default, and hardly any standard class has such qualified overload2 - I don't know of any but if there are, std::chrono::duration isn't one of them.

Note that the literal isn't modified. The left hand operand is a temporary object which in this case is discarded.


Another simple example of assigning an rvalue:

std::string{"temporary"} = "this is pointless, but legal";

1 This is actually a useful feature, although there aren't many use cases. Here is a useful example:

std::vector<bool> vec{true, false, true};
vec[1] = true; // vec[1] is an rvalue

2 Prior to C++11, there were no ref qualifiers, and thus all rvalues of assignable class types could be assigned. To maintain backward compatibility, the default cannot be changed, nor can pre-existing classes be changed (at least not without a deprecation period). std::chrono::duration was added in C++11, so it could have had qualified assignment operator, but standard authors appear to prefer not qualifying their assignment operators.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • It's simply not true that "literals ... are prvalues". String literals are lvalues, and there's nothing stopping you from writing a user-defined literal operator that returns an lvalue. – Brian Bi Aug 28 '20 at 19:40
  • @Brian I amended the answer. Would you agree it to be correct now? – eerorika Aug 28 '20 at 19:44
  • As for whether it's "intended", I wouldn't say that this feature specifically for these types specifically is "intended", though it is a consequence of more general rules that are. Is it "desirable"? No, not really, and this is what happens when you shoehorn language features into the library as if they were user code. – Asteroids With Wings Aug 29 '20 at 15:19
17

The std::chrono literals are not lvalues. They may appear as such, but what you are seeing is actually invocation of assignment operators by r-values. Why is this allowed? Specifically, because e.g. the member assignment operator

constexpr duration& operator+=(const duration& d)

as specified by [time.duration.arithmetic]/9, does not use any ref-qualifiers, meaning it does not reject being invoked by r-values.

Compare with the following example (implemented as duration above)

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) { return *this; }
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // OK!
}

as compared to the following example, which explicitly makes use of a & ref-qualifier to implicitly ban invocation on r-values:

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) & { return *this; }
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // error: no viable overloaded '+='
}

or, the following example, which explicitly deletes the r-value overload:

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) & { return *this; }
    constexpr Foo& operator+=(const Foo& d) && = delete;
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // error: overload resolution selected deleted operator '+='
}

For details regarding r-value qualifiers and how they could make sense to apply in order to ban to-rvalue-assignment, see e.g. the following Q&A:

dfrib
  • 70,367
  • 12
  • 127
  • 192