Обработка буквального нуля в коде, безопасном для единиц измерения

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

Так дано

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 а.е./две недели или любые другие единицы, которые вы хотите использовать (для скорости). Тогда возникает вопрос, как включить это и только это использование.

Явные операторы С++ 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.


person Ben Voigt    schedule 25.09.2013    source источник
comment
Для speed < 6 какой реальный вред в том, чтобы позволить 6 использовать те же единицы измерения, что и speed, неявно?   -  person jxh    schedule 26.09.2013
comment
@jxh: Помимо того, что это разрушит всю цель обеспечения безопасности юнитов? Запрет любой неявной ассоциации единиц измерения в этих библиотеках практически универсален, см., например, stackoverflow.com/a/17733452/103167.   -  person Ben Voigt    schedule 26.09.2013
comment
Я вижу, это может предотвратить ошибку в коде, если единицы измерения для самого speed изменятся.   -  person jxh    schedule 26.09.2013
comment
@jxh: Да, что особенно вероятно в коде шаблона, который может работать с входными данными с разными единицами измерения.   -  person Ben Voigt    schedule 26.09.2013
comment
Удивительный вопрос. Это определенно звонит в колокол универсального значения 0, которое C++ уже имеет для любого типа. до nullptr в С++ 11 NULL был определен как 0, а 0 можно присвоить числам с плавающей запятой и удвоить без предупреждения о преобразовании. 0 имеет особое универсальное значение в С++. Интересно, можно ли это использовать в вашем случае.   -  person v.oddou    schedule 26.09.2013
comment
@v.oddou: я использую это здесь, спрашиваю, действительно ли это хорошая идея, или я пропустил какой-то странный случай.   -  person Ben Voigt    schedule 26.09.2013
comment
@BenVoigt: У вас наконец-то есть ответ на этот вопрос сегодня?   -  person v.oddou    schedule 23.07.2014
comment
Инструменты статического анализа также будут жаловаться на использование 0 вместо nullptr для указателей.   -  person Mikhail    schedule 22.09.2016


Ответы (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;
}
person v.oddou    schedule 26.09.2013
comment
О, я определенно не хотел бы этого, поскольку это препятствует неявным преобразованиям. Даже если немного другой литерал нуля был бы хорош (что с пользовательскими литералами может быть не так уж и плохо), static_assert ужасен, и вместо него следует использовать SFINAE. В любом случае, как здесь помогает шаблон, не может ли тип второго параметра быть просто int_<0> ? - person Ben Voigt; 26.09.2013
comment
@BenVoigt: определенно, это просто результат нескольких шагов мутации при создании этого примера кода. Так что истерический изюм в принципе :) не жалко. Также int_‹0› может быть чем-то вроде вашего safe_zero_type, пустая структура отлично справится с этой задачей. Этот пример кода просто идиотский. Но это служит обсуждению. - person v.oddou; 26.09.2013

person    schedule
comment
Да, я это заметил. Была еще одна мысль, но оказалось, что nullptr также успешно преобразуется в типы указателей функций и членов. - person Ben Voigt; 26.09.2013
comment
о пункте 2: что, если вы сделаете оператор сравнения шаблонов, и если выведенный аргумент является целочисленной константой, вы можете статически утверждать против значения 0. - person v.oddou; 26.09.2013
comment
@v.oddou: я пробовал, не получилось. Но если у вас получится, отпишитесь. - person jxh; 26.09.2013
comment
Я думаю, вы правы, потому что фактический параметр функции не может быть constexpr, во время компиляции можно обрабатывать только параметры шаблона. - person v.oddou; 26.09.2013
comment
Я думаю, что единственно возможное решение должно использовать тот факт, что nullptr является std::nullptr_t, тогда как 0 просто преобразуется в единицу. Разрешение перегрузки может различаться между ними. Более того, 0 преобразуется в T::*(), как и nullptr, но больше ничего не делает. Таким образом, если существуют обе перегрузки, первая будет выбрана для nullptr, а вторая — только для 0. - person MSalters; 26.09.2013
comment
@MSalters: проблема в том, что 0 одинаково хорошо преобразуется в nullptr_t и любой тип указателя. Так что неоднозначно. Если у вас есть работающая демонстрация, я бы хотел ее увидеть. - person Ben Voigt; 26.09.2013
comment
@BenVoigt: Если бы у меня было решение, я бы опубликовал его как ответ, а не как комментарий;) - person MSalters; 26.09.2013