Комментарии 78
А как же Dart? Уже наигрались? /sarcasm
А если серьезно, то интересно, почему не взяли Rust
Ага. Синтаксис сильно похож, но, кажется, в Carbon не будет концепции владения.
P.s. Они хотят плавно переходить с С++, чтобы в одном проекте можно было безболезненно смешивать код на двух языках и потихоньку переписывать на новый. (Ну как Котлин с java). В таком разрезе концепция владения будет мешать.
А если серьезно, то интересно, почему не взяли Rust
Они об этом пишут в самом начале:
Existing modern languages already provide an excellent developer experience: Go,
Swift, Kotlin, Rust, and many more. Developers that can use one of these existing languages should. Unfortunately, the designs of these languages
present significant barriers to adoption and migration from C++. These barriers
range from changes in the idiomatic design of software to performance overhead.
То есть во главе угла возможность бесшовно использовать существующую кодовую базу на C++.
Google в Rust Foundation входит. Возможно решили не ставить на одного игрока
Странный плюсовый код. Впервые вижу подобную конструкцию auto main(int argc, char** argv) -> int { ...}
, применение лямбды там где не нужно (если это лямбда). Смысл в этом если можно просто int main() { ... }
. Вот для чего чего, но для точки входа это извращение.
И опять эти var-ы и двоеточия в типах и вывернутый на изнанку порядок объявления переменных.
Меня от такого синтаксиса тошнит. Что котлин не нравится, что свифт (из того что пробовал).
Причем тут лямбды вообще, в первую очередь данный синтаксис нужен был в С++11 для этого, а там уже он стал повсеместно использоваться.
template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
return lhs + rhs;
}
А использовать можно просто для консистенции стиля кода.
Lhs const& lhs
более консистентно
Не встречал, чтобы писали
int const NUM_BUCKETS = 16;
так проще читать объявления справа налево, что такое lhs? - справа налево это ссылка на константный Lhs
Если весь интернет пишет в исходниках
void doSmth(const std::string& s)
а вам нравится
void doSmth(std::string const& s)
вам придётся страдать, читая чужой код.
Ну и ваш код другими будет читаться со страданием, испытывая к нему подсознательную неприязнь.
Так и есть, страдаю :(
Проще читать то, что привычнее.
Привычки -- вещь изменяемая.
а вам нравится
Здесь дело не в "нравится", а в правильности, что ли.
То, что const
разрешено выносить за тип и там оставлять -- это нехорошее исключение из правил.
В чистом C можно вообще два раза написать const
, и это будет компилироваться (это ссылка). Оба const
'а здесь относятся к типу char
, косвенно это подтверждается формулировкой предупреждений компиляторов.
Если вы понаблюдаете за тем, где располагается const
в сложных производных типах, то увидите, что он всегда находится справа от сущности, на которую действует.
Если соблюдать это правило, то и ошибок меньше будет допускаться.
А кто там будет страдать от правильного написания -- это проблемы тех страдальцев.
В итоге, он создал язык D как эволюцию C++ и в нём разрешил обсуждаемую здесь неоднозначность, явно запретив «int const» в пользу «const int». Он то, наверное, получше нас язык чувствует?
В итоге, он создал язык D как эволюцию C++ и в нём разрешил обсуждаемую здесь неоднозначность, явно запретив «int const» в пользу «const int». Он то, наверное, получше нас язык чувствует?
Авторитетов не существует.
Александреску -- человек?
Значит, он тоже может ошибаться, вне зависимости от опыта?
И, значит, может иметь иррациональные предпочтения?
Пользуясь тем, что для многих он -- авторитет, и эти самые многие безоговорочно примут его точку зрения, потому что -- это же Александреску так сказал, он может "протолкнуть" не лучшие решения, но -- в пользу своих предпочтений (которые, кстати, со временем имеют свойство меняться).
Человек, потому что, здесь всё понятно.
Именно поэтому могут быть только сухие аргументы.
Никаких ссылок на авторитеты быть не может.
Кстати, вы заметили, что я в этом ответе ни на какой "авторитет" и не подумал даже пытаться ссылаться?
Та же самая попытка сослаться на чей-то авторитет.
Мало ли, кто что предпочитает.
Если вы понаблюдаете за тем, где располагается const в сложных производных типах, то увидите, что он всегда находится справа от сущности, на которую действует.Попробуйте привести пример. «Производный тип» у вас — это какой? Созданный наследованием?
Компилятору безразлично, «const T», или «T const», стандарт разрешает оба написания.
Верно.
Правильного, логически обоснованного ответа не существует
В том смысле, что "обязательно надо так, а не иначе" -- да, не существует.
и весь спор — чистая субъективщина.
Не совсем, есть объективные соображения за то, чтобы писать справа.
Во всех случаях, кроме рассматриваемого, const
действует на то, что слева. Мозг "обучается" и привыкает к этому.
Если теперь в рассматриваемом случае поступать наоборот, растёт вероятность допустить ошибку, потому что мозгу легче действовать на автомате.
Ну, и читать легче, когда всё единообразно.
Остаются только аргументы типа «так принято» и отсылки к авторитетам.
Отсылка к авторитетам не может быть аргументом.
Попробуйте привести пример. «Производный тип» у вас — это какой? Созданный наследованием?
Существуют базовые типы, производные типы, и типы, определяемые пользователем. В нашем случае типы, определяемые пользователем, ведут себя как базовые, поэтому их условно можно рассматривать как тоже базовые.
Производные типы образуются с помощью "указателей на", "массивов из", "функций, возвращающих" и, если берём C++, "указателей на member такого-то типа" и "ссылок на".
"Производность" типа можно рекурсивно "увеличивать" с некоторыми ограничениями. В частности, указатель может быть на любой производный тип, кроме ссылки, функция не может возвращать массив и функцию, массив не может состоять из функций и из ссылок, указатель на member не может быть на ссылку, ссылка тоже не может быть на ссылку.
Массивы, функции и ссылки не могут иметь собственной cv-квалификации, то есть, в частности, не могут быть const
.
Таковыми могут быть только указатели и указатели на member'ы.
Поскольку оба вида указателя обозначаются символом *
слева от сущности, и cv-квалификация задаётся между сущностью и *
, то получается, что const
всегда действует на то, что слева.
Например:
// ук-ль на ук-ль на int
int **p;
// ук-ль на ук-ль на const int
int const **p;
// ук-ль на const ук-ль на int
int *const *p;
// const ук-ль на ук-ль на int
int **const p;
// ук-ль на const ук-ль на const int
int const *const *p;
// const ук-ль на ук-ль на const int
int const **const p;
// const ук-ль на const ук-ль на int
int *const *const p;
// const ук-ль на const ук-ль на const int
int const *const *const p;
// ук-ль на ук-ль на const intВы уже на первом примере себе ломаете мозг. В коде пишете «int const», а в комментарии, т.е. так, как идёт ход ваших мыслей, как проговариваете у себя в голове, пишете «const int».
int const **p;
В остальном, общепринятая версия мне кажется более согласованной, когда const ставится перед тем объектом, который менять нельзя.
Ну, например
int * const p;
Тут нельзя менять значение p, но можно менять int, на который он указывает.А здесь наоборот
const int * p;
Можно менять значение p, оно не const, но нельзя менять int, на который указывает p, потому что это константный int.А если взять пример менее тривиальный?
int *const *p;
Если "в лоб" по вашей логике, то получается, что можно менять значение int
и значение p
, но нельзя менять значение второй слева *
.
Как устроены декларации?
Слева пишется тип, справа -- выражение.
Тип результата выражения, написанного справа, есть тип, написанный слева.
Пример:
int *p;
Каков тип выражения *p
?
Это написано слева: int
.
Какой должен быть тип у p
, чтобы выражение *p
имело тип int
?
Очевидно, что -- указатель на int
.
Для того, чтобы не гадать, а методично прочитать тип, необходимо идти в обратную сторону: если p
разыменовывается, то это -- указатель на что-то там.
При этом cv-квалификация указывается между операцией и действием:
int *const p;
То есть, операция *
применяется к p
, как бы, сквозь const
.
Поскольку операция разыменования -- префиксная, то внимание движется справа налево: p
, потом const
, потом *
.
Получается: p
есть const
'антный указатель на ... int
.
Если же написано:
int const *const p;
То читается при движении внимания справа налево так: p
есть const
'антный указатель на const
'антный int
.
Это, что касается проговаривания.
Не видно, чтобы мозг ломался.
>> int *const *p;Ну так и есть. Значение p и значение int можно менять.
Если «в лоб» по вашей логике, то получается, что можно менять значение int и значение p, но нельзя менять значение второй слева *.
Примерно так и раскручивается: p — указатель (не константный) на указатель (константный). То есть я группирую так:
int * (const *) p;
и получаю, что p указатель на const-объект.
Примерно так и раскручивается: p — указатель (не константный) на указатель (константный). То есть я группирую так:
int * (const *) p;
и получаю, что p указатель на const-объект.
Но правая *
относится к "p
— указатель (не константный)".
Не константный, а вы её пометили как (const *)
.
Правильная разметка — int (* const) * p;
.p
— указатель (не константный, правая *
) на указатель (константный, левая *
) на (не константный) int
.
Внимание движется справа налево, от p
к int
.
Если мы запишем сначала так:
int *const *p;
Затем заведём алиас на тип:
typedef int *T;
И перепишем первое, используя второе, то автоматически получим:
T const *p;
Как можно заметить, const
опять справа от того, на что он действует.
Вы считаете const частью типа (и это правильно, потому что const можно убрать под typedef).
Я же считаю const модификатором перед объектом, который нельзя модифицировать.
То есть, запись
int * const p;
Я логически группирую как int * (const p);
и читаю как «константный объект p, являющийся указателем на int».
А вы группируете как
int (* const) p;
И читаете как «объект p с типом 'константный указатель на int'».
Наверное, ваш подход ближе к тому, как компилятор видит код. Но лично я быстрее соображаю, когда имею дело с «константным объектом», чем с «объектом константного типа», потому что при попытке что-то сделать с p, во втором случае нужно пройти на уровень глубже и увидеть, что тип не позволяет его менять, а в первом случае я знаю, что сам объект не изменяемый.
Вы считаете const частью типа (и это правильно, потому что const можно убрать под typedef).
Буквально на каждом шагу в стандарте можно прочитать:
A pointer or reference to a cv-qualified type...
"cv-qualified type".
То, что const
является частью типа, ярче всего проявляется в определении множества переменных. Так не получится определить:
int a, const b{5};
вместо:
int a;
int const b{5};
Или -- наоборот, отменить const
, который указан для типа, для некоторых из переменных, определяемых через запятую.
То есть, запись
int * const p;
Я логически группирую как
int * (const p);
и читаю как «константный объект p, являющийся указателем на int».
Тогда, если следовать этой логике, необходимо писать:
int const i;
логически группировать как int (const i);
и читать как "константный объект i
, являющийся int
'ом".
Наверное, ваш подход ближе к тому, как компилятор видит код.
Да, поскольку квалификаторы являются частью типа по стандарту, а компиляторы следуют стандарту.
Но лично я быстрее соображаю, когда имею дело с «константным объектом», чем с «объектом константного типа», потому что при попытке что-то сделать с p, во втором случае нужно пройти на уровень глубже и увидеть, что тип не позволяет его менять, а в первом случае я знаю, что сам объект не изменяемый.
Думаю, это вопрос перестройки мышления.
Я выбрал вариант, соответствующий стандарту, потому что, несмотря на начальные усилия, которые, возможно, придётся потратить, далее не возникает никаких нестыковок и прочих неожиданностей, и можно работать с производными типами любой сложности.
class X
{
int static a();
int virtual b();
};
Уж лучше единообразно — все модификаторы ставить как можно левее.
Меня от такого синтаксиса тошнит
Его не просто так добавили по принципу "красивое/не красивое" — текущий синтаксис определения типов в C++ сильно контексто-зависимый (в зависимости от контекста, можно одну и ту же конструкцию интерпретировать по-разному из-за ambiguity), что сильно усложняет грамматику, компилятор и добавляет когнитивной нагрузки разработчику. Выбранный синтаксис избегает таких проблем.
текущий синтаксис определения типов в C++ сильно контексто-зависимый (в зависимости от контекста, можно одну и ту же конструкцию интерпретировать по-разному из-за ambiguity)
Смотрю именно поэтому в карбоне для типизации шаблонов используются круглые скобки, как и в случае вызова функции)
Это называется trailing return type и он может быть использован в любой функции
Вообще, это нынче считается идиоматическим С++ кодом, и лично мне нравится. И нет, это не лямбда.
Хотелось бы например писать fn, чтобы как-то показать, что объявляется функция, но увы, комитет не склонен добавлять новые зарезервированные слова. auto был зарезервирован в C ещё в 80-х, и в C++11 нашёл новое применение.
Интересно, почему не сделали синтаксис просто без ключевого слова перед именем ф-ции. Это приводит к каким-то неоднозначностям?
Согласен, что это семантически тупо, но в целом, меня не особо парит. Не самая убогая вещь в С++.
Интересно, почему не сделали синтаксис просто без ключевого слова перед именем ф-ции. Это приводит к каким-то неоднозначностям?
Ну в C так и было. Если не указан тип, значит, это int.
Но вообще да, неоднозначностей в C++ хватает:
https://en.wikipedia.org/wiki/Most_vexing_parse
Собственно, поэтому я бы всегда использовал короткое ключевое слово для обозначения функции.
Это называется trailing return type. Используется обычно тогда, когда тип возвращаемого значения является шаблонным и зависит от шаблонных типов параметров функции.
Это костыль времён C++11, когда не было автоматического вывода типа функции, а по каким-то причинам decltype
слева от имени нельзя было писать.
Сейчас смысла в этом костыле больше нет.
Сейчас смысла в этом костыле больше нет.
Не надо тарапыдзе. Вот типичный пример:
struct S1 {};
struct S2 { S2(S1) {} };
template<class T>
auto f(T t1, T t2) -> decltype(t1 + t2)
{
return t1 + t2;
}
void f(S2, S2)
{
}
int main()
{
f(S1{}, S1{});
}
В таком виде overload resolution работает как надо, выбирается вторая по счету перегрузка f(S2, S2)
. Теперь уберите trailing return type у первой (шаблонной) перегрузки (оставив только return type deduction) и посмотрите, что будет. Подумайте, почему.
Ну я бы так сделал:
template<class T>
concept Addable = requires (T a, T b) { a + b; };
struct S1 {};
struct S2 { S2(S1) {} };
template<Addable T>
auto f(T t1, T t2)
{
return t1 + t2;
}
void f(S2, S2)
{
}
int main()
{
f(S1{}, S1{});
}
Ваш выглядит лаконичнее и не вводит лишнюю сущность. Но при этом в случае более сложных требований к типу его применять сложнее.
template<class TScalar, class TMaxtrix>
auto f(TScalar a, TMaxtrix x) -> decltype(a*x)
{
return a*x;
}
Чет очень сильно похож на go.
ещё есть третьи, которым фиолетово :-)
тут офигенная концепция "можно переписать кусок приложения на новом коде". например: это конечно здорово, что pycharm мне пишет "а мы тут от питона 3.X отказываемся и не будем его нормально поддерживать". мне в этот момент что делать с сотнями проектов на 3.X? а если мне туда надо фичу небольшую добавить - перелопачивать весь проект? с карбоном это будет так: окей, есть старый проект, можешь новую фичу написать на карбоне, и всё будет работать. если когда-то дойдешь до "переписать весь проект" - отлично, а если нет - то ты можешь фичу уже сейчас сделать более быстро-безопасно, а остальное оставить как есть.
Главное чтобы не перепутали буквы, как в переводе ГТА...
Ну вот, только хотел начать Rust или Go изучать, а уже новый придумали....
Один вопрос – зачем? Зачем постоянно создавать новые пародии на другие языки? Чем вам не хватает rust или c++/c#? Вы серьёзно думаете, что этот новый язык ближайшие 10 лет будет кто-то изучать? ?
Ну потому что мировоззрение, парадигмы и стили меняются. Меняется железо. IDE и компиляторы становятся умнее, программисты теперь, не все как один - серые бородатые ботаны, а дерзкие бородатые со смузи, яркие индивидуальности... Что-то можно сделать лучше, какую-то работу оставить компилятору. Ну, может через 10 лет сделают сильный ИИ и тогда уже ни программисты ни сантехники больше не понадобятся. Питон например вышел око 30 лет назад, а выстрелил, только - лет 7-10 как. Это не угадаешь. Джаву тоже по началу хаяли.
Джаву тоже по началу хаяли.
Да и в Java есть ошибки в проектировании языка, которые не были очевидны в 90-х, и которые уже так просто не исправить, не поломав совместимость. И хотя Java не стоит на месте, к вопросам совместимости там относятся очень серьёзно. Отчасти поэтому и появились более новые языки для JVM.
Вы смотрели код компилятора Go, чтобы такое утверждать? А ведь он - открыт чуть более чем полностью.. нет там никакой "умности". Watcom C compiler был много умнее, как и gcc или иные .. Не далее как вчера полез смотреть что там за "80 попугаев" в Эскейп анализе. :(
Одна из причин почему я не люблю pascal и go, это синтаксис обьявления переменных с абсолютно избыточным ключевым словом и знаками.
var hello string
var A: Integer;
И они притащили этот атавизм в "новый" язык. Это конечно вкусовщина, но блин.. это же шкурка go, которую пытаются натянуть на c++, в этом он напоминает ironPython, который не шибко то популярен
Как я понимаю, var и let стоит принимать как 'auto ...' и 'auto const ...' соответсвенно. И тип можно не писать где он может быть выведен. Т.е. он опционален.
Меня больше дженерики смущают.
тип не удобно писать - когда он большой (шаблон типа map <>). в C++ ведь можно такfor (const auto& но на скрине Circle есть и в C++ и в Carbon
let не может передать разницу между 'const Circle* c' и 'Circle* const c';
Почему это атавизм? Какой вариант не "атавизм"?
В Go требуется писать var, только если переменная сразу же не устанавливается в какое-то значение. В 99% случаев это просто "hello := 5"
Убийца с++ №100501
от создателей убийцы с++ №100500
Я уж думал Dart станет преемником плюсов. По крайней мере синтаксически он куда больше похож на С++.
Убийца с++? Прошу в очередь.
Да боже ты мой... Я ещё Rust не выучил, а тут уже новый убийца C++ подъехал.
Можно помедленнее, я языки учить не успеваю.
И всё та же фобия угловых скобок и мания на круглые
Что значит "всё та же"? Почему вы думаете, что избегание угловых скобок это нечто иррациональное ("фобия", "мания")?
Угловые скобки усложняют парсер и делают его контексто-зависимым, т.к. A<B> C
можно интерпретировать по-разному: это то ли шаблон, то ли сравнение "больше/меньше". Именно поэтому в Go тоже отказались от них.
Ну ок, вы же новый язык придумываете — как насчёт того, чтобы определить целочисленные типы, явно задающие возможность переполнения
Они даже до типа Vector2/4 не додумались, хотя SSE/AVX уже в каждом утюге есть
Это звучит как ваши рандомные хотелки, почему именно они должны быть в языке, а не что-то другое? Как минимум, Vector2/Vector4 спокойно решается добавлением в stdlib как интринзика, не обязательно иметь в первой публичной версии.
Ну а в разделах «обработка ошибок» и «распараллеливание кода» нет вообще ничего, типа «присылайте нам свои идеи»
Если бы удосужились ознакомиться с проектом, то знали бы, что идея сделать проект публичным появилась всего 3 недели назад, чтобы раньше получить фидбек от сообщества — до этого язык делался приватно и разработка ещё не завершена.
Угловые скобки усложняют парсерВ C# как-то смогли решить проблему угловых скобок. Круглые скобки, внезапно, тоже контекстно-зависимы и используются и для группировки, и для приведения типов, и для передачи аргументов в функцию. Знак умножения тоже контекстно-зависим и расшифровывать конструкции из указателей на указатели вперемешку со скобками и перегруженными операциями умножения — отдельное специальное удовольствие в плюсах.
Это звучит как ваши рандомные хотелкиЭто звучит как хотелки, которые уже реализованы в других языках — в частности Фортране, D и HLSL.
спокойно решается добавлением в stdlib как интринзикаИнтрисики — это костыль, код с которыми ещё менее читаем, чем код непосредственно на ассемблере. Поддержка векторных типов на уровне языка не только облегчает синтаксис, но и упрощает кодогенерацию и оптимизацию.
Если бы удосужились ознакомиться с проектом, то знали бы, что идея сделать проект публичным появилась всего 3 недели назад, чтобы раньше получить фидбек от сообщества — до этого язык делался приватно и разработка ещё не завершена.Что и подтверждает исходный тезис. Ведь язык делался не просто приватно, а за зарплату от гугла. Иначе при чём тут гугл вообще?
предоставление средств для безопасной работы с памятью, использование
которых позволяет защититься от уязвимостей, вызванных обращением к
области памяти после её освобождения, разыменованием нулевых указателей и
выходом за границы буфера.
Если средства для безопасной работы с памятью только "предоставляются", а не навязываются, как в Расте, то никто их использовать не будет. И в итоге опять появится куча линтеров, статических анализаторов, фузеров и так далее, которые пост-фактум будут искать ошибки, и находить (как обычно) очень малую часть.
Попытки навязывания в расте регулярно проваливаются, потому что в задачах, связанных с высокой производительностью (да и не только в них, а вообще в чем-то сложнее хелловорлда), относительно регулярно появляются запросы на такие механизмы работы с памятью, которые примитивный борроу чекер раста обеспечить не может. Такова селява.
Так это пример работы с голыми указателями. В таких условиях в любом языке можно допустить кучу ошибок.
Совершенно верно. Раз есть работа с голыми указателями, значит, была потребность в создании возможности такой работы. И чем тут помогли потуги раста в навязывании "средств безопасной работы"? Ничем. Если в таких механизмах на определенном этапе возникает потребность, то они безусловно будут реализованы, и никакое навязывание тут не спасет, а если навязывание станет ну очень назойливым, то будет просто выбран другой язык, в котором этого навязывания нет.
а что с go ?
Google развивает язык программирования Carbon для экспериментальной замены C++