C++の負債とCarbon
はじめに
修正や追加等はコメントまたはGitHubで編集リクエストをお待ちしております。
本題
最近Googleが発表したCarbonと言う言語をご存知でしょうか?
これはC++を改善するために生まれた言語です。
ですがRustやZigみたいに全て書き換える必要は無いです。
TypeScriptとJavaScriptのように相互に運用できる言語設計(になる予定)です。
では何故Carbonが必要だったのでしょうか?
Carbonは生き残れるのでしょうか?
C++を知ることで、Carbonを使う理由が見えてくると思います。
C++で作られた or 依存しているもの
(間接的なもの含む)
- V8
- Windows
- Mac
- Linux
- Python
- LLVM
- Swift
- Ruby
- Adobe製品
- Microsoft Office
- JVM
ご覧ください
プログラミングの根幹を支えているのはC++です。
人類はC++に依存しています。
C++の欠点
構文がかけ離れすぎてる
Cが生まれたの約半世紀前
一方今使われているC以外の言語は10~20年前ぐらいに生まれました。
1983年(39年前)に生まれたC++は現代のプログラミング言語とは全く別の構文を持っています。
例えば以下のようなプログラムがあります。
#include <iostream>
int main() {
int age = 18;
std::cout << "I am " << age << " years old." << std::endl;
return 0;
}
fn main() {
let age = 18;
println!("I am {} years old.", age);
}
function main() {
let age = 18;
console.log("I am " + age + " years old.");
}
main();
def main():
age = 18
print("I am " + age + " years old.")
if __name__ == "__main__":
main()
C++を見てください。
print,printf,console.logのどれでもなくstd::coutとかいう意味わからん言葉です。
(coutはcharacter output(文字出力)の略らしいです)
構文も ()
ではなく <<
を使う特殊な構文です。
メモリを自分で管理しないといけない
C/C++のメモリ管理はプログラマーの責任です。
自分で管理できるので自由度が高い反面考慮しなければいけないことが増えます。
#include <iostream>
int main(int argc, const char **argv)
{
char *name = new char[32];
memset(name, 0, 33);//オーバーフロー
return 0;
}
上記コードのようにメモリ操作で思わぬオーバーフローを起こすことがあります。
コンパイラは上記コードでは何の警告もしません。
自由と責任が常にC/C++に付きまといます。
C 互換ではない
C言語とC++は混在して書けるので互換性があると思いがちですがそうではないです。
簡単な例を挙げるとすれば auto
です。
Cの auto
はローカル変数の意味で型を省略すると int
で解釈されます。
C++の auto
は型推論で解釈されます。
したがって下記のようなコードはCとC++で違いがでます。
#include <iostream>
int main(int argc, const char **argv)
{
auto age = 9.5;
std::cout << "I am " << age * 2 << " years old." << std::endl;
return 0;
}
#include <stdio.h>
int main(int argc, const char **argv)
{
auto age = 9.5;//int型なので0.5は切り捨て
printf("I am %f years old.\n", age * 2);// 18になる
return 0;
}
Rust と Carbon どっち?
RustはC/C++の代わりになることを目的としています。
メモリ安全なコードを簡単に書けるのにC/C++とほぼ同等な速度を持っています。
ですが独特で難解な構文があり、人類には少し早すぎます。
Carbonのサイトには。
If you can use Rust, ignore Carbon(もしRustが使えるなら、Carbonは無視して)
とあるのでRustが書ける強強マンにはCarbonは無用です。
C++は書けるけどRustは無理だなーって人向けの言語がCarbonです。
まとめ
まだまだ赤ちゃんのCarbonの今後の成長が楽しみです。
個人的にはV8などでCarbonが採用されたら一気に注目されると思います。
Discussion
Linuxは主にC言語で書かれています。少なくとも、kernelはC++に依存していなかったかと。
配布物としては、Qt / KDEを含み、C++に依存することが多いかも知れません。
ご指摘ありがとうございます。
修正しました。
C++23以降は
std::print
やstd::println
がありますから,標準入出力の1点張りでこの主張を展開するのはやや無理があると思います.記事執筆時点でリリース済みのGCC12では警告されますから,この記述は誤りです.
記事のメンテナンスができておらず申し訳ございません
C++23でC++も進化してたのですね…もはやあの構文は古の構文ということに驚いております
コンパイラの方は当時ローカルで実験してから書いたのですが、執筆時の私の環境のバージョンが古かったのかも知れません
ご指摘いただきました点は修正させていただきます
コメントありがとうございます!
<<
に書式化出力の意味を割り当てたのは特殊ですが、<<
自体はごく普通の演算子です。 特殊な構文と言ってしまうとやや語弊があるかと思います。このへんの背景としてはむしろ特殊にしないために
<<
を使う方式が考え出されました。Ada の議論の中で書式化出力を簡潔で型安全に実現するには言語がそれをサポートする専用の機能が必要だとする主張があり、それをチャレンジとして受け取った Stroustrup はその時点での C++ の文法の範囲内で iostream を作りました。 C++ の言語機能を何も変えることなくライブラリとして実現したのです。 特殊なことをする必要はありませんでした。
ではなぜ関数呼出しの形にしなかったのかというと、その時点では可変長引数を型安全に扱えなかったからです。 C++11 で variadic template が用意されるまでは C 方式の可変長引数を使うしかなく、 C 方式の可変長引数は型情報を持たないことによって実現されるというとんでもない素朴な方式です。 実引数と仮引数の整合性はプログラマが取るしかなく、不整合があってもコンパイラが検出できないことがほとんどです。 C++ 的には型安全を損なう方式には消極的にならざるを得ません。
そして書式文字列と各引数の整合性が取れているかをコンパイラが検証するにはコンパイル時に文字列を解釈する必要もあり、
constexpr
の充実も必要でした。それらの下準備が済んでようやく
std::print
に至ったわけです。書式化出力の「書き方」としては関数の形式のほうが良いとは皆が思ってたと思います。 でも出来なかったので仕方がなかったんです。
C23 以降の
auto
は型推論するようになって C++ に近づきました。 推論されるのは変数宣言のみなので C++ ほどではないですが。また、結果的な動作としては型推論なのですが C++ の
auto
はプレースホルダ型指定子であり、要するに型指定子を書ける箇所にauto
を書ける仕組みで、 C23 からのauto
はそれまで通り記憶域クラス指定子のままという違いもあります。C++ では
auto
で型推論する文法を導入するときにそれまでと意味を変えてしまいましたが、 C では過去の文法と互換性を取りつつ上手く新しい機能と整合性を取る形です。