Как я могу создать новый примитивный тип с сильными typedefs в стиле С++ 11?
Я пытаюсь эмулировать в С++ a distinct
типа из Nim
язык программирования. Следующий пример не будет
компилировать в Nim, потому что компилятор ловит переменные e
и d
имеющие разные типы (Error: type mismatch: got (Euros, float)
), несмотря на
оба являются float на двоичном уровне:
type
Euros = distinct float
when isMainModule:
var
e = Euros(12.34)
d = 23.3
echo (e + d)
Один из способов сделать это в С++ - это написать класс-оболочку для float. <Удаp > Но
это не работает с API, которые экспортируют этот тип, потому что размер не будет
быть таким же, как float. Или даже если размер класса соответствует длине хранения
float, он никогда не будет соответствовать размеру типа char. Это будет работать, если вы также реализуете все возможные операторы для операций, таких как сложение, вычитание и т.д., но требует много типизации и дублирования кода.
Предыдущие вопросы типа Создание нового примитива
типа
имеют принятый ответ на использование boost strong typedef. Однако typedef
похоже, работает только для сигнатур типа функции, typedef не будет препятствовать двум
флоат-унаследованные типы, которые должны быть добавлены вместе, и их тип полностью изменился
(ну, потому что есть только иллюзия нового типа):
#include <boost/serialization/strong_typedef.hpp>
#include <stdio.h>
BOOST_STRONG_TYPEDEF(float, money);
void test(money a, float b)
{
int t = a + b;
printf("value is %d", t);
}
int main()
{
money a(5.5);
int euros(5);
// This is not caught!
int dollars = a + euros;
printf("dollars %d\n", dollars);
// But the compiler catches this misuse.
test(euros, a);
}
Но что почти, вызов test()
не будет работать, потому что подпись
не соответствует, но язык по-прежнему позволяет другим операциям манипулировать типами
по желанию.
В том же ответе упоминается С++ 0x, чтобы принести сильные typedefs, поэтому я искал это
новая поддержка и обнаружили, что Сам Бьярне Страуструп дал стиль С++ 11
основной
2012.
Около минуты 21 он начинает говорить об этих новых сильных typedefs. если ты
скачайте только слайды, страница 19 начнет говорить о единицах СИ и позже
на стр. 22 и 23 упоминается, как это будет сделано. Однако я не смог
заставьте примеры работать. Здесь лоскутное одеяло, которое мне удалось придумать:
template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};
template<typename Unit> // a magnitude with a unit
struct Value {
double val; // the magnitude
explicit Value(double d) : val(d) {} // construct a Value from a double
};
using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value< Unit<1,0,-1> >; // meters/second type
constexpr Value<Second> operator "" _s(long double d)
// a f-p literal suffixed by ‘_s’
{
return Value<Second> (d);
}
constexpr Value<Meter> operator "" _m(long double d)
// a f-p literal suffixed by ‘_m’
{
return Value<Meter> (d);
}
int main(void)
{
Speed sp1 = 100_m / 9.8_s;
return 42;
}
Я пытаюсь скомпилировать это под MacOSX с последним Xcode 5.1.1 с командной строкой:
$ g++ unit.cpp -std=c++11
unit.cpp:13:25: error: constexpr function return type 'Value<Second>' is not a
literal type
constexpr Value<Second> operator "" _s(long double d)
^
unit.cpp:5:8: note: 'Value<Unit<0, 0, 1> >' is not literal because it is not an
aggregate and has no constexpr constructors other than copy or move
constructors
struct Value {
^
unit.cpp:18:24: error: constexpr function return type 'Value<Meter>' is not a
literal type
constexpr Value<Meter> operator "" _m(long double d)
^
unit.cpp:5:8: note: 'Value<Unit<1, 0, 0> >' is not literal because it is not an
aggregate and has no constexpr constructors other than copy or move
constructors
struct Value {
^
unit.cpp:26:20: error: no matching literal operator for call to 'operator "" _m'
with argument of type 'unsigned long long' or 'const char *', and no
matching literal operator template
Speed sp1 = 100_m / 9.8_s;
^
unit.cpp:26:28: error: no matching literal operator for call to 'operator "" _s'
with argument of type 'long double' or 'const char *', and no matching
literal operator template
Speed sp1 = 100_m / 9.8_s;
^
4 errors generated.
Возможно, примеры, приведенные в слайдах, и мне не хватает кода? Есть ли у кого-нибудь полный пример того, что Бьярн пытался продемонстрировать?
Ответы
Ответ 1
Есть несколько способов решить эту проблему, но поскольку я искал исправление для кода Bjarne в слайдах презентации, я принимаю этот ответ, который @robson3.14 оставил в комментариях на вопрос:
#include <iostream>
template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};
template<typename Unit> // a magnitude with a unit
struct Value {
double val; // the magnitude
// construct a Value from a double
constexpr explicit Value(double d) : val(d) {}
};
using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value<Unit<1,0,-1>>; // meters/second type
// a f-p literal suffixed by ‘_s’
constexpr Value<Second> operator "" _s(long double d)
{
return Value<Second> (d);
}
// a f-p literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(long double d)
{
return Value<Meter> (d);
}
// an integral literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(unsigned long long d)
{
return Value<Meter> (d);
}
template<int m1, int k1, int s1, int m2, int k2, int s2>
Value<Unit<m1 - m2, k1 - k2, s1 - s2>> operator / (Value<Unit<m1, k1, s1>> a, Value<Unit<m2, k2, s2>> b)
{
return Value<Unit<m1 - m2, k1 - k2, s1 - s2>>(a.val / b.val);
}
int main()
{
Speed sp1 = 100_m / 9.8_s;
std::cout << sp1.val;
}
Ответ 2
В С++ 11 нет сильных typedef. Существует поддержка единиц с <chrono>
, но это совершенно другая вещь. Никто не может договориться о том, какое поведение должны иметь сильные typedefs, так что никогда не было предложений для них, которые попали куда угодно, поэтому они не только не являются ни С++ 11, ни С++ 14, нет реальной перспективы в этом время, когда они попадут в любой будущий Стандарт.
Ответ 3
Компиляторы С++ обычно ожидают вариант командной строки -std=c++11
(или -std=c++0x
для более старых, соответственно) для активации поддержки С++ 11.
не поддерживает стиль С++ 11 вообще.
Нет, это прекрасно. Поддержка GCC 4.7.2 может быть отмечена здесь. Чтобы активировать некоторые экспериментальные функции, пройдите -std=gnu++11
.
И Clang 3.4 фактически поддерживает практически все в С++ 11 и уже много из С++ 1y.
Ответ 4
Не уверен, что это то, чего ты хочешь, он уродлив, но он работает:)
Вы можете обернуть тип в класс шаблона,
template <typename T, int N> // N is used for tagging
struct strong_typedef
{
using strong_type = strong_typedef<T,N>; // typedef for the strong type
using type = T; // the wrapped type
T value; // the wrapped value
strong_typedef(T val): value(val){}; // constructor
strong_typedef(){value={};}; // default, zero-initialization
// operator overloading, basic example:
strong_type& operator+(const strong_type& rhs)
{
value+=rhs.value;
return *this;
}
// display it
friend ostream& operator<<(ostream & lhs, const strong_typedef& rhs)
{
lhs << rhs.value;
return lhs;
}
};
затем используйте его как
// these are all different types
strong_typedef<double, 0> x = 1.1;
strong_typedef<double, 1> y = 2.2;
strong_typedef<double, 2> z = 3.3;
std::cout << x + x << std::endl; // outputs 2.2, can add x and x
// cout << x + y << endl; // compile-time ERROR, different types
x
, y
и z
теперь три разных типа из-за разных N
-s, используемых в шаблоне. Вы можете получить доступ к типу и значению с помощью полей type
и value
, например x::value
(будет double 1.1). Конечно, если вы прямо typedef
struct_typedef::type
, вы вернетесь к квадрату, так как теряете тип strong
. Поэтому в основном ваш тип должен быть strong_typedef
, а не strong_typedef::type
.
Ответ 5
Одна альтернатива, которая не включает typedef и не строго соблюдается компилятором, но очень затрудняет программисту ошибаться в кодировании рассматриваемого модуля в качестве члена структуры.
Для моего проекта Arduino у меня есть такие типы, как
template <typename T>
struct millisecond {
T millisecond;
static constexpr const struct millisecond<T> zero = { 0 };
};
template <typename T>
struct microsecond {
T microsecond;
static constexpr const struct microsecond<T> zero = { 0 };
};
и использовать как
auto time_diff = millisecond<unsigned long>::zero;
time_diff.millisecond = nowMilliseconds() - s_lastPollTime.millisecond;
Так что с этой стратегией компилятор не мешает вам смешивать модули, но если вы это сделаете, то ошибка всегда будет кричать на вас:
total_expenses.euros = expence1.euros + expence2.dollars;