Обработка литерального нуля в единичном коде

За очень немногими исключениями (глядя на вас, шкалы температуры Фаренгейта и Цельсия) единицы являются линейными, а значение нуля одновременно является аддитивным тождеством для всех единиц сразу.

Итак, данный

auto speed = dimensioned(20, _meter/_second);
auto power = dimensioned(75, _watt);

затем

if (speed < 6) ...
if (power > 17) ...

не имеет смысла, чем

if (speed > power) ...

вам следует написать

if (speed < dimensioned(6, _mile/_hour)) ...

Однако это имеет смысл:

if (speed < 0)

потому что 0 м/с == 0 миль/ч == 0 A.U./fortnight или любые другие единицы, которые вы хотите использовать (для скорости). Тогда возникает вопрос, как включить это и только это использование.

С++ 11 явных операторов и контекстное преобразование в bool избавились от необходимости идиомы "safe-bool". Похоже, эта проблема может быть решена с помощью сопоставимой идиомы "безопасного нуля":

struct X
{
  int a;
  friend bool operator<(const X& left, const X& right) { return left.a < right.a; }
private:
  struct safe_zero_idiom;
public:
  friend bool operator<(const X& left, safe_zero_idiom*) { return left.a < 0; }
};

К сожалению, похоже, что развернутые библиотеки размерности/единицы не делают этого. (Этот вопрос возник, потому что я действительно хотел проверить, был ли std::chrono::duration отрицательным). Это полезно? Есть ли случаи, которые могут привести к его провалу? Есть ли более простой способ разрешить сравнение с нулем?

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


Я отмечаю, что он позволяет

 X{1} < nullptr

как допустимое выражение:( и, к сожалению, предоставление недоступной перегрузки типа std::nullptr_t не исправляет это, поскольку стандарт говорит в разделе 4.10

Константа нулевого указателя интегрального типа может быть преобразована в prvalue типа std::nullptr_t.

Ответ 1

  • Да. Вы очень легко убедили меня, что это полезно.
  • Вы уже указали точку отказа, для nullptr. Я не мог думать ни о чем другом. Мои попытки разработать механизм запрета nullptr, но позволить 0 все приводили к сложным схемам, которые не работали. В принципе, поскольку невозможно указать С++, вам нужен параметр функции constexpr, это сложно (я пока не могу сказать...), чтобы создать функцию, которая принимает аргумент int, но приводит к компиляции если значение аргумента не равно 0.
  • Если вы согласны с разрешением nullptr, то более простой реализацией будет использование std::nullptr_t напрямую, а не отдельный класс safe_zero_idiom. (По общему признанию, это не так безопасно, поскольку в вашей реализации нет доступа к типу safe_zero_idiom.)

struct X
{
  int a;
  friend bool operator<(const X& left, const X& right) { return left.a < right.a; }
  friend bool operator<(const X& left, std::nullptr_t) { return left.a < 0; }
};

Ответ 2

Я мог только придумать очевидное решение, которое отклоняется от того, что вы хотите:

#include <stdexcept>
#include <iostream>
#include <type_traits>
using namespace std;

#include <boost/mpl/int.hpp>

using namespace boost::mpl;

struct X
{
    int a;

    friend bool operator<(const X& left, const X& right)
    {
        return left.a < right.a;
    }

    template< typename T >
    friend bool operator<(const X& left, T zero)
    {
        static_assert( is_same<int_<0>, T>::value, "cannot compare against arbitrary things");
        return left.a < 0;
    }
};

int_<0> unitless0;


int main()
{
    X x;

    //if (x < 3) cout << "oopsie";  // we get a build error here as excpected.

    if (x < unitless0)
        cout << "ok";

    return 0;
}