Allow to handle literal zero in a special way


-- Introduction --

Literal zero is a special constant in C++. It is not only an integer value of zero, but it also represents a null pointer and can be freely converted to any other numeric type without warnings. However, there is currently no way to recognize literal zero as an argument for a function, which might come convenient in some cases.

-- Problem statement --

Consider a class that represents a type-safe length:

class Length
  static Length meters(double x) { return Length(x); }
  static Length millimeters(double x) { return Length(x / 1000); }

  friend bool operator<(const Length& lhs, const Length& rhs)
    return lhs.d_meters < rhs.d_meters;

  explicit Length(double meters) : d_meters(meters) {}

  double d_meters;

We can construct length given a number and a dimension (meters or millimeters in this example). We can compare two lengths, but not a length and a scalar:

  auto a = Length::meters(5);
  auto b = Length::millimeters(500);
  std::cout << (a < b) << std::endl;  // Properly compiles: compare two lengths
//  std::cout << (a < 5) << std::endl;  // Properly does not compile: cannot compare length and a scalar

This is great, but there is one special case of a scalar - zero. And it seems quite natural to comapre a length with zero without specifying its dimension! Consider:

  std::cout << (a < 0) << std::endl;  // Should compile: compare length with zero

One way to allow such comparison is to exploit the fact that literal zero can be implicitly converted to a pointer. Or to nullptr_t. We can add a constructor to Length class to do this:

  Length(std::nullptr_t) : d_meters(0) {}

This trick allows us to construct Length from literal zero, and since constructor is implicit, it also allows us to compare Length with literal zero.

This method has drawbacks though. First, it also makes the following line legal, which makes no sense:

  std::cout << (a < nullptr) << std::endl;  // Improperly compiles: makes no sense to compare length with nullptr

Second, it confuses static analyzers, which are aware of the fact that nullptr_t stands for pointers, and you should use nullptr instead of zero.

Third, it also confuses reader, since nullptr_t relates to pointers and is not expected in a class representing a physical entity.

See demo here:

-- Proposed solution --

Introduce new utility type std::literal_zero_t, which might be constructed only from literal zero. Given this type, we can replace constructor accepting std::nullptr_t with constructor accepting std::literal_zero_t in a previous example. This will solve all the listed problems.

  Length(std::literal_zero_t) : d_meters(0) {}

-- Links --

6 комментариев
дичь какая-то)
что мешает добавить
statis Length zero() { return Length{0}; }
и сравнивать длину с длиной?

auto a = Length::meters(5);
std::cout << (a < Length::zero()) << std::endl;

а вообще лучше для различных мер сделать аналогично std::chrono::duration<>
develoit, так более многословно. Для математика такая запись - дичь. Чтобы воспринять "0" нужно меньше ментальных усилий, чем "Length::zero()".
Сергей Вахреев
Имхо плохая идея. Если не нравится писать Length::meters(0), то можно сделать user-defined literal для всех мер. И код строго типизирован, и выглядит коротко и понятно
Сергей Вахреев
Сергей Вахреев, для user-defined literal придётся указывать суффикс всегда.
можно сделать через user-defined literals, но будет не 0, а что-то вроде 0_m.
Antervis, именно, нужно будет писать суффикс.
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).