同じ記号使いすぎ問題({} & , ()など) 【C++トラップ#2】


はじめに

C++で同じ記号が使われていることの紹介と、私の見解を載せています。
気軽に見ていってください。


C++でまず面倒くさいのが、同じ記号同じような場面で使わなければならないことです。
これによってエラーがグーンと見づらくなっているし、コンパイラ側も実装が面倒だろうし、プログラマ側も多少配慮をしないとなので面倒です。

初心者の混乱の元(「この&はアドレスなんだけど、ここにつけたら参照って意味になるんだよ」みたいな)にもなります。

私は他の言語をガチガチにやっていないので、他の言語でも結構ややこしい問題になっている言語もあるかもしれません。

第一にC++は、記号に過ぎず、classstructなど、"同じ"といったものがまあまあありますね。

,問題

a[10, 20];
f(10, 20, 30);
f((10, 20, 30));

cppmap - operator[] に複数の引数

,が、カンマ演算子,なのか、区切りのカンマ,なのか…
operator[]では、確かに構文的にはどちらもありえてしまいます。

どちらかといえば、引数区切りはかなり頻繁にするので、少しマイナーな使い方っぽいカンマ演算子,が削られるのではないでしょうか。

私もカンマ演算子,は1行にまとめたりするときに「便利だな」とは思いますが、正直使いたくはありませんし、最近は全く使いません。

{}問題

{ // ブロック
  int a[] = { 10, 20, 30 }; // 配列
  Sample s = { 10, "abc", 10 }; // オブジェクト(構造体)
  int val = { 10 }; // オブジェクト
}

{}は、ブロック配列初期化子の主に3つの意味があります。

ブロックはまあいいとして、配列初期化子は同じに当たるもので、同じ場面で使います。

構造体初期化はSample{}の様に構造体名を添えるだけで確定的に出来ますが、
配列は上のvalの様に、値が1つだと配列かどうかわからないのでどうしようもありません。

特に構造体の配列ときたらもう嫌になってきます。

コンパイラ側も、少し複雑な構文ではあるので、C++で見づらいエラーの1つでもあります。

その他

その他、& : < > () * []など、たくさーんあります。
(-> ->) (>> >2つ)など、組み合わせ技もあります。

思いついたやつをてきとうにまとめてあります。(思ったよりいっぱいありました)

アンパサンド &
  • アドレス演算子 &a
  • 参照修飾子 int& a
  • AND演算子 a & b
  • 論理積演算子 a && b
  • ラムダ式の参照 [&a]
int val = 10;

int& a = val;// 参照
int* p = &val;// アドレス演算子
int b = 1 & 2;// AND
bool c = true && false; // 論理積
const auto f = [&a, &p] () {}; // ラムダ式の参照
コロン :

たくさんありますが、使用場所が結構違うので混乱はしないかもしれません。

  • gotoのラベル GOTO_LABEL:
  • switchcaseラベルとdefaultラベル case 0: default:
  • スコープ解決演算子(:2つ) std::
  • 三項演算子 true ? 10 : 20
  • 継承 : public Sample
  • アクセス指定子 public:
GOTO_LABEL:// gotoラベル
switch (0) {
case 0:// caseラベル
default:// defaultラベル
}
std::cout;// スコープ解決演算子
int a = true ? 10 : 20;// 三項演算子
struct Sample : public Sample2 {// 継承
public: private: protected: // アクセス指定子
};
丸括弧 ()
  • 関数宣言のの引数部分 int a(int a, int b) [](int a, int b) {}
  • defineマクロの引数部分 #define A()
  • 関数呼び出し演算子 a(10, 20) f()
  • define関数マクロの呼び出し A(a, aaa), A(10, 20)
  • 式の優先をする括弧 (10 + 20) * 2
  • 各ステートメントの括弧 if (true) for(;;)
int a(int a, int b) { return a + b; }// 関数宣言のの引数部分
#define MACRO(a, b)// defineマクロの引数部分

a(10, 20);// 関数呼び出し演算子
MACRO(a, aaa); // define関数マクロの呼び出し

int v0 = (10 + 20) * 2; // 式の優先をする括弧

// ステートメントの括弧
if (true) {} else if (false) {}
for ( ;; ) {}
while (false) {} do {} while (false);
switch (0) {}
try {} catch (...) {}

if (Sample(a(10, (10 + 20) * 2), 10, 20)) {} // 死ぬほど見づらいやつ
角括弧 []
  • 添字演算子 a[10]
  • 属性 [[maybe_unused]] [[deprecated]]
  • ラムダ式 []() {}
a[10];// 添字演算子
[[maybe_unused]] int a; // 属性
const auto f = []() {}; //ラムダ式
小なり 大なり < >
  • 大なり、小なり、大なりイコール、小なりイコール演算子 10 < 20 40 >= 50
  • アロー演算子 p->m
  • template引数 template<class T, size_t n> f<int, 10>()
bool a = 10 < 20;// 小なり 大なり
bool b = 10 <= 10;// 小なりイコール 大なりイコール
p->m;// アロー演算子
template<class T> void f(); // template引数
f<int>();// template引数(呼び出し)
int a = 10 << 1; // シフト演算子

おわりに

使いづらいC++と古いC言語

C++は「C言語との互換」という重りを将来ずっと引きずっていかなければならない言語なのかもしれません。
モダンな言語に追いつけるよう、魅力的な機能追加は毎アップデートされていますが、所詮土台はC言語なので、「C言語という頭の固いおっさんにTikTokを教え込む」に過ぎません…[1]

namespacetemplatemoduleも、私が思うに「もう少し使いやすくてもいいのにな…」と結構思います。

今後の開発の参考になれば幸いです。
ありがとうございました。

脚注
  1. 「すべておっさんがTikTokを使いこなせない」といった意味はありません。そして頭の固いおっさんやおっさん差別する目的はありません。あくまで一般的な見解をもとにした例です。不快に思われたら申し訳ありません。 ↩︎