Приведение типов
От: NeoCode  
Дата: 27.02.14 13:45
Оценка: -1
Пытаюсь систематизировать операторы приведения типов. На примере С/С++ и подобных языков "среднего" уровня.

В С++ есть 5 способов приведения типов — один сишный и 4 оператора cast.
Понятно что сишный остался ради совместимости, он неудобен тем что очень сложно его искать в коде (нет ключевого слова), т.е. с точки зрения дизайна языка такой оператор нежелателен.
А какое количество операторов приведения типов вы считаете разумным, если бы мы могли перепроектировать С++ заново?

Очевидно, что должен быть оператор динамического приведения (dynamic_cast), который проверяет возможность приведения, используя дополнительную информацию.
Но: такой оператор может возвразать null (нулевой указатель или в общем случае значение по умолчанию типа, к которому приводим), а может выкидывать исключение. То есть уже как-бы два оператора.

Статическое преобразование типа.
Должен быть некий "тривиальный" оператор, который явно приводит то, что компилятор неявно приводить не хочет. Аналог static_cast и частично приведения в стиле Си.
Фактически — приведение для устранения ворнингов (не ошибок). Расширение типа (из int в double), потеря точности (из double в int), во всех случаях когда есть возможность явного преобразования но нет возможности неявного и т.п.

reinterpret_cast.
Вероятно, должен быть некий сверхнизкоуровневый оператор для приведения через область памяти (чего угодно к чему угодно). Мало ли.

С константностью:
Как я понимаю, идеологически правильнее иметь два типа константности: истинную (объект создается один раз и далее его невозможно изменить никогда) и локальную (явно запрещаем изменять объект в данном контексте). Если обратиться к языку D, то первое — это immutable, второе — const. Хотя я бы сказал что второе это "readonly".
В связи с этим для преобразования из immutable в изменяемый объект — только reinterpret_cast (реально низкоуровневый оператор для хакерских целей). Можно, например, обратиться к памяти и попытаться изменить константный литерал.

Для снятия readonly — должен быть какой-то отдельный оператор типа const_cast. Это более серьезно чем статическое приведение, это не динамическое приведение, и это менее серьезно чем reinterpret_cast.

Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, которы я здесь не учел?
Re: Приведение типов
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 27.02.14 14:20
Оценка:
Здравствуйте, NeoCode, Вы писали:

NC>Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, который я здесь не учел?


Я бы оставил только статическое приведение на этапе компиляции (в которое бы попали static, const и reinterpret), и динамическое на этапе выполнения кода. А так да, с одной стороны эти преобразования типов можно отделить. Но какая от этого практическая польза? Почему должно быть что-то отдельное? Приведение типов выполняется на так уж и часто, более того по двум типам более-менее понятно, как их можно привести один в другой. Очень похоже на случай "лишний член — жопе непонятка".

Не говоря про то, что можно
// (1) заменить конструкторами/методами для числовых типов
int32_t value1 = -3;
printf("%u", uint32_t(value1));

// (2) ввести метод address для преобразования адресов
typedef const void * ptr_t;
uintptr_t & address(ptr_t &);
void * ptr = malloc(32);
address(struct_ptr) = address(ptr);
Re[2]: Приведение типов
От: NeoCode  
Дата: 27.02.14 15:22
Оценка:
Здравствуйте, Mystic, Вы писали:

M>Я бы оставил только статическое приведение на этапе компиляции (в которое бы попали static, const и reinterpret), и динамическое на этапе выполнения кода. А так да, с одной стороны эти преобразования типов можно отделить. Но какая от этого практическая польза? Почему должно быть что-то отдельное? Приведение типов выполняется на так уж и часто, более того по двум типам более-менее понятно, как их можно привести один в другой. Очень похоже на случай "лишний член — жопе непонятка".


Когда есть методы — это хорошо, лично для меня методы нагляднее всего. Но они есть не всегда.
Почему разные? Ну ИМХО потому что они действительно разные. Например, из float в int можно привести "статически", а можно через "reinterpet" (это будет эквивалентно *(int*)&float_var). Т.е. reinterpret — это некая низкоруровневая вещь, которую я бы рассматривал скорее рядом с ассемблерными вставками, а не как приведение типов.
const_cast — это для явного обхода запрета на модификацию. Хотя как раз насчет const_cast у меня большие сомнения в целесообразности.
Практическая польза — в том, что их можно найти в коде поиском. Иногда при больших изменениях кода (особенно чужого) это бывает полезно.
Хотя эти ужасные длинные названия мне не нравятся, надо придумать что-то покороче.

Про dynamic: в некоторых случаях невозможность приведения — нормальная ситуация и null допустим. В некоторых — нет, и нужно исключение. Хотя это может быть более общий вопрос дизайна языков программирования...
Re: Приведение типов
От: AlexRK  
Дата: 27.02.14 17:18
Оценка:
Здравствуйте, NeoCode, Вы писали:

NC>Пытаюсь систематизировать операторы приведения типов. На примере С/С++ и подобных языков "среднего" уровня.


Лучше всего обойтись без приведений типов вообще.

Максимум — оставить один аналог dynamic_cast, возвращающий null или Option при неудаче. (А вообще нуллы тоже желательно выкинуть.)

Static cast это тупость, можно заменить на вызов функции (пусть даже известной конпелятору).

Reinterpret — ну, это если язык совсем низкоуровневый.
Re[3]: Приведение типов
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 27.02.14 17:38
Оценка:
Здравствуйте, NeoCode, Вы писали:


NC>Например, из float в int можно привести "статически", а можно через "reinterpet" (это будет эквивалентно *(int*)&float_var).

Через reintepret у меня не получилось:

строка

int32_t y = reinterpret_cast<int32_t>(x);


дает

error: invalid cast from type ‘float’ to type ‘int32_t {aka int}’


Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов.

NC>const_cast — это для явного обхода запрета на модификацию. Хотя как раз насчет const_cast у меня большие сомнения в целесообразности.


Пару раз сталкивался из-за кривости дизайна.

NC>Практическая польза — в том, что их можно найти в коде поиском. Иногда при больших изменениях кода (особенно чужого) это бывает полезно.


Не сталкивался с таким. Зачастую есть более насущные проблемы. Ну получу я списки static_cast и reinterpret_cast отдельно. И что это даст? Не говоря про то, что часто static или reinterpret выбирается по настроению. Надо один указатель привести к другому. Если один из них void *, то вполне подойдет static_cast. Но и reinterpret_cast вполне себе работает. Как для меня явно не то место, где следует заморачиваться. Тем более, что если возникает нужна что-то приводить на постоянной основе, то пишется wrapper.
Re[4]: Приведение типов
От: NeoCode  
Дата: 28.02.14 17:16
Оценка:
Здравствуйте, Mystic, Вы писали:

M>Через reintepret у меня не получилось:


Да, ваш пример не работает, зато работает например такой (со ссылкой). Почему такое ограничение — не знаю, все равно reinterpret же, какая уж разница раз лезем на битовый уровень
float x = 3.14;
unsigned int &y = reinterpret_cast<unsigned int&>(x);


В LLVM есть инструкция bitcast, которая, судя по описанию, делает то же самое (кстати, красивое название для этого оператора, лучше чем громоздкий reinterpret_cast).

M>Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов.


union требует объявления явной дополнительной переменной.
Re[5]: Приведение типов
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 28.02.14 17:45
Оценка:
Здравствуйте, NeoCode, Вы писали:

NC>Здравствуйте, Mystic, Вы писали:


M>>Через reintepret у меня не получилось:


NC>Да, ваш пример не работает, зато работает например такой (со ссылкой).

Со ссылкой понятно, и с указателями можно намутить так же.

NC>В LLVM есть инструкция bitcast, которая, судя по описанию, делает то же самое (кстати, красивое название для этого оператора, лучше чем громоздкий reinterpret_cast).

В C есть memcpy для таких вещей. Можно было бы поднять его на уровень компилятора.

M>>Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов.

NC>union требует объявления явной дополнительной переменной.
Ну и что? Можно подумать, что reinterpret_cast это одна из самых частоиспользуемых возможностей языка, что позволит сэкономить кучу места. А так необходимость возникает раз в год, два раза на пасху. Можно и переменную описать, чтобы мозг не засорять. Заодно битовые поля могут сделать код более читаемым:

/**
 * bitScanForward
 * @param 64 bit unsigned integer value
 * @return index (0..63) of least significant one bit
 *         -1023 if passing zero
 */
int bitScanForward(U64 bb)
{
   union {
      double d;
      struct {
         unsigned int mantissal : 32;
         unsigned int mantissah : 20;
         unsigned int exponent : 11;
         unsigned int sign : 1;
      };
   } ud;
   ud.d = (double)(bb & -bb); // isolated LS1B to double
   return ud.exponent - 1023;
}
Re: Приведение типов
От: SV.  
Дата: 02.03.14 04:54
Оценка: -1
Здравствуйте, NeoCode, Вы писали:

NC>Пытаюсь систематизировать операторы приведения типов. На примере С/С++ и подобных языков "среднего" уровня.


NC>В С++ есть 5 способов приведения типов — один сишный и 4 оператора cast.

NC>Понятно что сишный остался ради совместимости, он неудобен тем что очень сложно его искать в коде (нет ключевого слова), т.е. с точки зрения дизайна языка такой оператор нежелателен.

Возьмите &. Одна и та же кракозябра используется и для создания нового типа, и для взятия адреса. И нельзя поискать "&" и найти все объявления типов или все взятия адреса отдельно друг от друга. Что, теперь будем говорить, что & неудобен, нежелателен с точки зрения на дизайн языка (а не точки зрения дизайна, кстати — у дизайна зрения нет)? Ах, вы ищете конкретные ссылочные типы Type&? Ну, так и приведение люди ищут к какому-то конкретному типу, а поиск "(Type)" даст куда более вменяемые результаты.

Раз заговорили про дизайн, есть два диаметрально разных подхода: максимально понизить порог вхождения, а там пусть мучаются, либо наплевать на порог вхождения, зато в повседневке максимально облегчить жизнь. Ну, а между ними весь спектр языковых решений. Очевидно, Си подходил к программисту со второй меркой: пусть текст на нем весьма далек от английского языка, один раз выучишь все эти &, *, (), ||, &&, зато потом при чтении не будешь отвлекаться. Подход, по сути, классически математический. Наша школьная учительница математики стебала позднесоветские учебники за многословное доказательство теорем. У нее запись с кракозябрами кванторов занимала ровно столько строчек, сколько шагов в доказательстве, а каждая строка состояла из кучки символов. Так вот, мода городить ключевые слова вместо кракозябр — это уже не Си, а смещение к противоположному полюсу. Чтобы без мануала люди могли начать писать код. И надо себе в этом отдавать отчет.

Не знаю, как сейчас, а в старину приверженность создателей первому полюсу и дала Си выиграть с ошеломляющим счетом. Только не надо снова начинать про никс и связанную с ним фору у языка. Ознакомьтесь с историей вопроса. В Microsoft, Apple и других крупных компаниях безоговорочно рулил Pascal. С ключевыми словами. Вот это была фора, так фора, что не помешало ему слиться. Читаешь мемуары, постоянно видишь — «да, он был красивый, но многословный».

NC>А какое количество операторов приведения типов вы считаете разумным, если бы мы могли перепроектировать С++ заново?

NC>Очевидно, что должен быть оператор динамического приведения (dynamic_cast), который проверяет возможность приведения, используя дополнительную информацию.

Ровно один — из Си. В той же форме, без ключевых слов, по соображениям, изложенным выше. Только значить он должен что-то одно, в зависимости от языка.

dynamic_cast в C++ — это настоящая бомба. Его реализовали в C# (в виде сишного оператора, конечно — с пониманием подошли люди), но там это сделали грамотно: во-первых, динамический кастинг встроен в среду, которая неотделима от языка, во-вторых, это если и не единственный кастинг (в unsafe-части не силен), то уж точно основной в 99%. (as это не новый способ приведения, а сокращение от "obj is T ? (T) obj : null"). А что в C++? А вот что. Я лично фиксил баг одного умника, который за два месяца навтыкал повсюду dynamic_cast, а потом свалил с работы. А в мобильном проекте RTTI был выключен. В результате, программа просто грохалась на каждом. Чем думали люди, которые вводили в язык операторы, требующие соблюдения внешних условий? Короче, вот вам формальный вызов: от динамического кастинга в плюсах вреда (как в приведенном примере) больше, чем пользы. Чтобы было наоборот, нужна неотделяемая от языка среда, в которой типам уделялось бы повышенное внимание (с метаданными, и полным описанием каждого типа).

На всякий случай, отдельно: не надо перекладывать ответственность на изготовителей компиляторов, типа, стандарт прокукарекал, а они не подтянулись. Сам по себе RTTI чужероден в идеологии C++ минимального обязательного оверхеда. Его не случайно под мобильные платформы делают опциональным. А подумать об этом бардаке можно было еще в комитете.

NC>Но: такой оператор может возвразать null (нулевой указатель или в общем случае значение по умолчанию типа, к которому приводим), а может выкидывать исключение. То есть уже как-бы два оператора.


А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.

NC>reinterpret_cast.

NC>Вероятно, должен быть некий сверхнизкоуровневый оператор для приведения через область памяти (чего угодно к чему угодно). Мало ли.

Для подобных махинаций с памятью (a la fast square root из Q3) должны использоваться указатели либо ничего. А облегчать написание подобных хаков отдельным оператором?

NC>С константностью:

NC>Как я понимаю, идеологически правильнее иметь два типа константности: истинную (объект создается один раз и далее его невозможно изменить никогда) и локальную (явно запрещаем изменять объект в данном контексте). Если обратиться к языку D, то первое — это immutable, второе — const. Хотя я бы сказал что второе это "readonly".
NC>В связи с этим для преобразования из immutable в изменяемый объект — только reinterpret_cast (реально низкоуровневый оператор для хакерских целей). Можно, например, обратиться к памяти и попытаться изменить константный литерал.
NC>Для снятия readonly — должен быть какой-то отдельный оператор типа const_cast. Это более серьезно чем статическое приведение, это не динамическое приведение, и это менее серьезно чем reinterpret_cast.

Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.

NC>Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, которы я здесь не учел?


Если это считать продуманностью, то майданную революцию придумал Черчилль в восемнадцатом году.

P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу. А современный C++ это какая-то адская жесть. Я сильно подозреваю, что народу не хватает C#, но 1) общественного, а не микрософтного, 2) с ручным управлением памятью или, хотя бы, полноценно настраиваемым GC, или, хотя бы, профилируемым. Вот и пытаются соорудить из C++. Получается не очень.
Re[2]: Приведение типов
От: NeoCode  
Дата: 03.03.14 08:21
Оценка:
Здравствуйте, SV., Вы писали:

SV.>А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.


ИМХО RTTI это маленький кусочек недоделанной рефлексии. Если бы ее нормально доделали, была бы очень полезная вещь, многие извращенные велосипеды отпали бы. Не вижу почему это чуждо идеологии С++, просто почему-то вектор развития языка пошел в другую сторону.

SV.>Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.


Ну вот я примерно об этом. const при передаче ссылок/указателей в функции — это именно readonly (мы говорим, что в данном контексте переменная по этой ссылке не должна изменяться). Это именно право доступа, как private/public. При этом остается const для переменных (именованные константы), там константность связана с объектом в течение всего его времени жизни. Тут тоже две ситуации: или объект действительно иммутабельный, или проще закрыть доступ для изменения всем, кроме "избранных". Последняя ситуация ни в одном известном мне языке программирования отдельно не рассматривается. А если объект на самом деле, глобально и по дизайну — immutable, но надо поменять — только через прямой низкоуровневый доступ к памяти и вся ответственность на программисте.

SV.>P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу.


Лично я динамическое приведение использую. Например, есть дерево полиморфных объектов. Чтобы для определенного элемента найти в нем родителя определенного типа, иду в цикле от данного элемента по parent-указателям и проверяю dynamic_cast'ом, приводятся ли они к нужному типу. Так что динамическое приведение — вещь нужная.
Хотя не спорю, приведение в стиле C# красивее, при разработке нового языка программирования конкретно для динамического приведения лучше использовать именно оператор "as".

SV.>А современный C++ это какая-то адская жесть.

Здесь согласен. Но жесть в основном с шаблонами, которые используют не по назначению. С остальным никакой жести не вижу.

SV.>Я сильно подозреваю, что народу не хватает C#, но 1) общественного, а не микрософтного, 2) с ручным управлением памятью или, хотя бы, полноценно настраиваемым GC, или, хотя бы, профилируемым. Вот и пытаются соорудить из C++. Получается не очень.


Ну это да.
Re[3]: Приведение типов
От: SV.  
Дата: 03.03.14 09:57
Оценка:
Здравствуйте, NeoCode, Вы писали:

SV.>>А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.


NC>ИМХО RTTI это маленький кусочек недоделанной рефлексии. Если бы ее нормально доделали, была бы очень полезная вещь, многие извращенные велосипеды отпали бы. Не вижу почему это чуждо идеологии С++, просто почему-то вектор развития языка пошел в другую сторону.


Это чуждо C++ потому, что код, в котором не нужно отражение, будет иметь оверхед в виде, как минимум, одного дополнительного указателя на каждый объект. Иначе, как вы получите тип после компиляции? Кроме того, как быть с имеющимся кодом в виде бинарей + заголовочных файлов? Опять необязательно работающие операторы вводить? Спасибо, одного dynamic_cast'а хватило. Отражение в дотнете работает и приносит пользу потому, что оно там изначально и сквозно реализовано, а скромный оверхед на фоне богатых языковых возможностей там никого не е (C# не позиционируется как язык для написания производительного кода, он позиционируется как язык для RADостей).

SV.>>Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.


NC>Ну вот я примерно об этом. const при передаче ссылок/указателей в функции — это именно readonly (мы говорим, что в данном контексте переменная по этой ссылке не должна изменяться). Это именно право доступа, как private/public. При этом остается const для переменных (именованные константы), там константность связана с объектом в течение всего его времени жизни. Тут тоже две ситуации: или объект действительно иммутабельный, или проще закрыть доступ для изменения всем, кроме "избранных". Последняя ситуация ни в одном известном мне языке программирования отдельно не рассматривается. А если объект на самом деле, глобально и по дизайну — immutable, но надо поменять — только через прямой низкоуровневый доступ к памяти и вся ответственность на программисте.


Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?

SV.>>P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу.


NC>Лично я динамическое приведение использую. Например, есть дерево полиморфных объектов. Чтобы для определенного элемента найти в нем родителя определенного типа, иду в цикле от данного элемента по parent-указателям и проверяю dynamic_cast'ом, приводятся ли они к нужному типу. Так что динамическое приведение — вещь нужная.


И зачем вам это нужно?

NC>Хотя не спорю, приведение в стиле C# красивее, при разработке нового языка программирования конкретно для динамического приведения лучше использовать именно оператор "as".


SV.>>А современный C++ это какая-то адская жесть.

NC>Здесь согласен. Но жесть в основном с шаблонами, которые используют не по назначению. С остальным никакой жести не вижу.

Ну так те же кастинги.
Re[4]: Приведение типов
От: NeoCode  
Дата: 03.03.14 11:07
Оценка:
Здравствуйте, SV., Вы писали:

SV.>Это чуждо C++ потому, что код, в котором не нужно отражение, будет иметь оверхед в виде, как минимум, одного дополнительного указателя на каждый объект. Иначе, как вы получите тип после компиляции? Кроме того, как быть с имеющимся кодом в виде бинарей + заголовочных файлов? Опять необязательно работающие операторы вводить? Спасибо, одного dynamic_cast'а хватило. Отражение в дотнете работает и приносит пользу потому, что оно там изначально и сквозно реализовано, а скромный оверхед на фоне богатых языковых возможностей там никого не е (C# не позиционируется как язык для написания производительного кода, он позиционируется как язык для RADостей).


Рефлексия, требующая дополнительных указателей в объектах (как и многие другие подобные вещи), должна быть опциональной и включаться/отключаться соответствующими ключевыми словами для каждого конкретного модуля, класса и функции. Понятно что в С++ этого уже не сделать, но у нас же раздел "философия", а не "с++".

SV.>Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?


Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему.
Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.
Re[5]: Приведение типов
От: SV.  
Дата: 03.03.14 12:45
Оценка: -1
Здравствуйте, NeoCode, Вы писали:

NC>Рефлексия, требующая дополнительных указателей в объектах (как и многие другие подобные вещи), должна быть опциональной и включаться/отключаться соответствующими ключевыми словами для каждого конкретного модуля, класса и функции. Понятно что в С++ этого уже не сделать, но у нас же раздел "философия", а не "с++".


А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.

SV.>>Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?


NC>Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему.

NC>Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.

Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже. То же относится к коду с деревом и подбором, про который вы не ответили. Я просто вспоминаю, что в плюсах я никогда не использовал RTTI и отлично себя чувствовал, а в шарпе использовал, конечно, но в таких ситуациях, где все решал динамически-бинарный характер платформы, которой у плюсов все равно нет.

И самое главное,

>охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни)


Такие попытки я вижу в C++ и считаю их утопией. По мне, так лучше бы C++ оставили без изменений, а вся эта энергия была бы направлена на создание свободной альтернативы C# с повышенной гибкостью в области управления памятью (хотя бы с профилями GC, а лучше с ручным управлением GC). Хотя, конечно, и это утопия. Майкрософт подошел к вопросу по-диктаторски, поэтому у них так изящно и красиво все сделано. Комитетом ничего подобного не создать никогда, а то, что создают отдельные энтузиасты, так и лежит на полке — некому проталкивать.
Re[6]: Приведение типов
От: NeoCode  
Дата: 04.03.14 06:57
Оценка:
Здравствуйте, SV., Вы писали:

SV.>А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.


Скачал библиотеку, а там лямбда-функции, которые у тебя не поддерживаются. Или шаблоны. Или обработка исключений (компилятор Borland C 3.11 насколько я помню не поддерживает ) Что делать???

SV.>Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже.


Насколько я знаю, reinpterpret_cast введен именно для того, чтобы "потенциально опасные" операции работы с указателями сделать явными и заметными в коде. Но с вашей логикой можно пойти дальше и спросить — а зачем private и protected, если можно взять адрес объекта, привести к какому-нибудь unsigned char* и фигачить в память
А с const_cast я не могу привести никаких примеров (ни положительных ни отрицательных), но я жду, когда кто-то приведет пример когда он нужен. Т.к. сказать "не нужно" можно вообще про что угодно, и никакой полезной информации это не даст, а на реальных примерах можно уже и понять, нужен он или не нужен.

SV.>То же относится к коду с деревом и подбором, про который вы не ответили. Я просто вспоминаю, что в плюсах я никогда не использовал RTTI и отлично себя чувствовал, а в шарпе использовал, конечно, но в таких ситуациях, где все решал динамически-бинарный характер платформы, которой у плюсов все равно нет.


Пример с деревом можно переписать, я знаю: в базовый класс ввести поле тега и соответствующий enum, в дочерних классах инициализировать это поле соответствующими значениями, а в функции поиска родителя нужного типа — искать по этому тегу. Но каждый раз, когда я буду добавлять новый тип, мне нужно будет добавлять тип и в enum, в случае с динамик кастом оно само работает. В этом и смысл рефлексии — если информация есть в компиляторе, зачем заставлять программиста ее прописывать еще раз явно во всяких енумах, массивах и т.п.?

SV.>Такие попытки я вижу в C++ и считаю их утопией. По мне, так лучше бы C++ оставили без изменений, а вся эта энергия была бы направлена на создание свободной альтернативы C# с повышенной гибкостью в области управления памятью (хотя бы с профилями GC, а лучше с ручным управлением GC). Хотя, конечно, и это утопия. Майкрософт подошел к вопросу по-диктаторски, поэтому у них так изящно и красиво все сделано. Комитетом ничего подобного не создать никогда, а то, что создают отдельные энтузиасты, так и лежит на полке — некому проталкивать.


Я примерно такой энтузиаст и именно этим занимаюсь. Just for fun. Получится, не получится — пофиг, но по крайней мере это повод подумать над интересными вопросами.
Re[7]: Приведение типов
От: SV.  
Дата: 04.03.14 07:22
Оценка:
Здравствуйте, NeoCode, Вы писали:

SV.>>А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.


NC>Скачал библиотеку, а там лямбда-функции, которые у тебя не поддерживаются. Или шаблоны. Или обработка исключений (компилятор Borland C 3.11 насколько я помню не поддерживает ) Что делать???


Вы говорите о breaking changes в языке. С ними будет одно из двух. Или они входят в заголовочный файл (интерфейс библиотеки), и у вас просто не соберется проект, или они входят в либу, и все будет работать без проблем, а вы об этом просто не узнаете.

А я говорю о том, что вам пришел указатель из либы через самый простейший интерфейс, и вы даже не знаете, что вам на нем вернет typeof и не закрашится ли в этом месте программа. Причем, в принципе.

Разница понятна?

SV.>>Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже.


NC>Насколько я знаю, reinpterpret_cast введен именно для того, чтобы "потенциально опасные" операции работы с указателями сделать явными и заметными в коде. Но с вашей логикой можно пойти дальше и спросить — а зачем private и protected, если можно взять адрес объекта, привести к какому-нибудь unsigned char* и фигачить в память


Извините, не люблю передергиваний, так что, дальше пас.
Re[8]: Приведение типов
От: NeoCode  
Дата: 04.03.14 08:01
Оценка:
Здравствуйте, SV., Вы писали:

SV.>А я говорю о том, что вам пришел указатель из либы через самый простейший интерфейс, и вы даже не знаете, что вам на нем вернет typeof и не закрашится ли в этом месте программа. Причем, в принципе.


SV.>Разница понятна?


Почему закрашится? При правильной реализации просто вернет null и все. Другое дело, что такое поведение должно быть описано в стандарте языка и правильно реализовано в компиляторах. Но это вполне реально.
Это тоже что и с любыми интерфейсами (COM-объекты всякие и т.п.): в одной версии там нет какого-то интерфейса, в следующей версии добавили. Нет — значит нет, получаем нуль.
Re[5]: Приведение типов
От: Кодт Россия  
Дата: 17.03.14 13:17
Оценка:
Здравствуйте, NeoCode, Вы писали:

NC>Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему.

NC>Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.

const_cast нужен для того, чтобы нарушать контракт строго определённым образом. Так же, как static_cast — другим строго определённым способом (приведение вниз по иерархии).
Сам дизайн этих кастов, безусловно, навеян языком Си. Поэтому я предпочитаю велосипеды вида
// T выводится
template<class T> T& unconst_ref(T const& t) { return const_cast<T&>(t); }
template<class T> T* unconst_ptr(T const* t) { return const_cast<T*>(t); }

// To указывается, From выводится
template<class To, class From> To& downcast_ref(From& t) { static_assert(is_base_and_derived<To,From>::value); return static_cast<To&>(t); }
template<class To, class From> To& downcast_ptr(From* t) { static_assert(is_base_and_derived<To,From>::value); return static_cast<To*>(t); }


С учётом сдвига базы, даункаст и реинтерпрет могут дать разные результаты.


Вот зачем два разных конста — тут можно поспорить.
Сколько бы ни было константностей, их всегда можно законным образом сломать через алиасы
struct Foo
{
  void change();
  bool unchanged() const;
};

Foo* global;
void f(Foo const& x) // обещаем, что не будем менять x
{
  global->change(); // нагло соврали!
}
void test_f()
{
  Foo foo;
  global = &foo;
  assert(foo.unchanged());
  f(foo);
  assert(foo.unchanged()); // увы
}


class Bar
{
  Foo const* member;
public:
  explicit Bar(Foo const* m) : member(m) {}
  Foo const* see_but_not_change() const { return member; }
  bool unchanged() const { return member->unchanged(); }
};
Foo the_foo;
const const const Bar the_bar(&the_foo); // клянёмся, что Bar неизменен

void test_b()
{
  assert(the_bar.unchanged());
  the_foo.change(); // пробрались во внутреннее состояние и сломали его
  assert(the_bar.unchanged()); // увы
}
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.