Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[52.205.159.48] |
|
Сообщ.
#1
,
|
|
|
Всем привет!
Начну с общеизвестных утверждений, ну или типа того ... - Чем хороши языки со статической типизацией? - Статическая типизация хороша в качестве помощи прогеру, т.к. позволяет искать ошибки на этапе кодирования - Чем плохи языки со статической типизацией? - Статическая типизация заставляет прогера в ряде случаев делать дополнительные нецелевые действия - Чем хороши языки со динамической типизацией? - Динамическая типизация позволяет прогеру в ряде случаев не делать дополнительные нецелевые действия - Чем плохи языки со динамической типизацией? - Динамическая типизация плоха, т.к. не помогает прогеру в поиске ошибок на этапе кодирования Ну вот кажется С++ со статической типизацией ... строгой ее назвать "рука не поднимается" (С) Черномырдин А все из-за POD-типов. Пример 1 #include <iostream> using Counter = int; using Size = int; Counter GetCount(Counter value) { return (value > 0) ? value*2 : 0; } Size GetSize(Size value) { return (value > 0) ? value : 0; } int main() { Counter count = 1; Size size = 2; std::cout << GetSize(size)*GetCount(count) << std::endl; return 0; } Код компилируется, запускается, даже что-то считает. Но по названиям видно - смешалось несмешиваемое. Ибо наши using/typedef не образуют новые типы, а только лишь создают алиасы. Ну и как нам контролировать уместность/законность передачи, использования там-сям переменной POD-типа? ну чтобы уж точно сказать "мне помогает статическая типизация чисто конкретно, а не просто конкретно" Пример 2 Ну подумал-подумал и запилил тяп-ляп вот какой говнокод: #include <iostream> enum class EnumType {Counter,Size}; template <typename T, EnumType E> class ClassInteger { public: T value; ClassInteger(T init) : value(init) {} bool operator>(ClassInteger& other) { return value>other.value;} bool operator>(int other) { return value>other;} bool operator<(ClassInteger& other) { return value<other.value;} bool operator<(int other) { return value<other;} ClassInteger& operator=(const int other) { value=other; return *this;} ClassInteger& operator=(const ClassInteger& other) { value=other.value; return *this;} ClassInteger& operator*(const int i) { value *=i; return *this;} ClassInteger& operator*(const ClassInteger& i) { value *=i.value; return *this;} }; template <typename T, EnumType E> std::ostream& operator<<(std::ostream& os, const ClassInteger<T,E>& i) { os << i.value; return os; } using Counter = ClassInteger<int,EnumType::Counter>; using Size = ClassInteger<int,EnumType::Size>; Counter& GetCount(Counter& value) { return (value>0) ? value*2 : value*0; } Size& GetSize(Size& value) { return (value>0) ? value*3 : value*2; } int main() { Counter C1 = 1; Counter C2 = 2; std::cout << GetCount(C1)*GetCount(C2) << std::endl; Size S = 3; std::cout << GetSize(S) << std::endl; // std::cout << GetCount(C1)*GetSize(S) << std::endl; // <--- тут ожидаемая ошибка return 0; } В принципе - получилось что хотел - две переменные с одинаковыми "кишками", но разные по типам. И их уже непреднамеренно не смешаешь, не перепутаешь при передаче. Но реализация этой хотелки у меня получилась - дичь! Собственно вопрос к обсуждению. Как можно более изящно и без дополнительного оверхеда типизировать POD-типы в С++? |
Сообщ.
#2
,
|
|
|
Как по мне, это вполне себе изящное решение. Ты определил для компилятора идиому новых типов, описал их свойства и правила взаимодействия между разными объектами этой идиомы. На каком-нибудь Прологе проще не получилось бы.
P.S. У Александреску, например, был целый паттерн value2type, который он применял как раз для того, чтобы "преобразовывать" значения в типы. |
Сообщ.
#3
,
|
|
|
А как быть с оверхедом? Был тип, который влезал в регистр, а что стало?
|
Сообщ.
#4
,
|
|
|
А ничего не стало. Ты же поля в класс не добавлял. Для пущей уверенности ещё final можешь сделать, чтобы компилятор смог соптимизировать, если ты где-то передашь этот класс по константной ссылке.
|
Сообщ.
#5
,
|
|
|
Цитата OpenGL @ Ты же поля в класс не добавлял Как не добавлял? А "T value;"? |
Сообщ.
#6
,
|
|
|
Хорошо, не добавлял полей кроме T.
|
Сообщ.
#7
,
|
|
|
Цитата Qraizer @ Как по мне, это вполне себе изящное решение. Немного взял паузу - поразмыслил ... и все же нет! Ну если только - как "заготовка". И, кстати, я там малеха слукавил - ниже в рассуждениях увидишь. Вот смотри, распишу по пунктам: По второму вопросу - не знаю, может как-то через traits мутить, передавать его через третий шаблонный параметр? По третьему вопросу - вырисовывается нескончаемая портянка определения попарных операций, не? |
Сообщ.
#8
,
|
|
|
Цитата JoeUser @ Оверхед - и это важно. Оверхед зависит от того, сможет ли компилятор заинлайнить функции. Если заинлайнит, то никакого оверхеда у тебя не будет т.к. ему просто неоткуда больше взяться. |
Сообщ.
#9
,
|
|
|
JoeUser, раз уж ты задумался о единицах измерения, то это уже совсем другая парадигма. К типам она не имеет отношения, она посредством них может быть реализована. Т.е. самоцель уже не типизировать PODы, а как типы применить в качестве инструмента. Ты б сначала эту парадигму хорошо продумал, что ты хочешь ею решить, а потом уже искал методы реализации. На скорую руку: посмотри std::common_type.
|