ーオーバーロードークーイーズー

この記事は Acompany5周年アドベントカレンダー 22日目 の記事です.

はじめに

C++オーバーロードクーーーーーーーーーーーーーイズ!!!!ヒューヒュー!!!パチパチパチ!!!

例題

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

auto f() { std::cout << "A" << std::endl; }
auto f(int) { std::cout << "B" << std::endl; }

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは B です.
Aの関数はint型の値を受け取れないので呼ばれません.Bは定義通りなので正しく呼ばれます.

このように同じ関数名の2つの定義が与えられ,main関数で呼び出されるものはどちらか,もしくはコンパイルエラーかをあてる問題をいくつか用意しました.すべての問題はA,B,Compile Errorの3つの内のどれかが答えになります.言語はC++20を前提とします.
全問正解目指して頑張りましょう!

本題

問題1

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

auto f(int*) { std::cout << "A" << std::endl; }
auto f(long long) { std::cout << "B" << std::endl; }

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは Compile Error です.
A,Bのいずれもint型からの暗黙的な型変換が必要で,どちらも優先順位としては同等です.同じ優先順位のものが複数ある場合はオーバーロードの解決ができないため本コードはコンパイルエラーです.

問題2

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

struct C {
  C(int) {}
};

auto f(int*) { std::cout << "A" << std::endl; }
auto f(C) { std::cout << "B" << std::endl; }

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは A です.
A,Bのいずれもint型からの暗黙的な型変換が必要です.しかし暗黙的な型変換の中でも優先順位が付いており,この場合はポインタへの変換の方が優先度が高いためAが呼ばれます.
https://timsong-cpp.github.io/cppwp/n4861/over#ics.rank

問題3

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

struct C {
  C(int) {}
};
struct CCopy {
  CCopy(C) {}
};

auto f(C) { std::cout << "A" << std::endl; }
auto f(CCopy) { std::cout << "B" << std::endl; }

int main() { f(C(0)); }


選択肢:
A
B
Complie Error
解答答えは A です.
Aは直接コンストラクタを呼んでおり,Bはコピーコンストラクタを呼んでいます.オーバーロードの優先順位は前者の方が高いためAが呼ばれます.

問題4

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

auto f(long long) { std::cout << "A" << std::endl; }
auto f(...) { std::cout << "B" << std::endl; }

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは A です.
Aは暗黙的な型変換,Bは可変長引数への変換です.オーバーロードの優先順位は可変長引数が最も低いためAが呼ばれます.

問題5

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

auto f(long long) { std::cout << "A" << std::endl; }

template <class... T>
auto f(T...) {
  std::cout << "B" << std::endl;
}

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは B です.
Aは暗黙的な型変換,Bは可変引数テンプレートです.一見可変引数であるBの優先順位が低いように見えますが,あくまでテンプレート関数の中で優先順位が最低というだけで全体的な優先順位はテンプレートと同等です.テンプレートの優先順位は暗黙的な型変換よりも高いためBが呼ばれます.

問題6

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

template <class T>
auto f(T a) -> decltype(a++, void()) {
  std::cout << "A" << std::endl;
}

template <class T>
auto f(T a) {
  std::cout << "B" << std::endl;
}

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは Compile Error です.
いわゆるSFINAEです.Aは第一引数aに対して後置インクリメント演算子が実装されてることを要求しています.実際に呼び出しているint型には後置インクリメント演算子が実装されているため,Aは候補として残ります.Bは特に制約がないため候補として残ります.以上により2つの関数が候補として残っているためSFINAEは発生せずコンパイルエラーになります.

問題7

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

template <class T>
concept Addable = requires(T x) {
  x++;
};

template <Addable T>
auto f(T a) {
  std::cout << "A" << std::endl;
}

template <class T>
auto f(T a) {
  std::cout << "B" << std::endl;
}

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは A です.
いわゆるconceptです.前問と同じようにAは後置インクリメント演算子を要求しており,int型で呼び出しているため問題なく呼び出せます.Bも同様のため一見コンパイルエラーのように見えますが,テンプレート関数の中でもconceptにより解決される関数は優先順位が高く設定されています.そのため本問題ではAが呼ばれます.

問題8

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

template <class T>
concept Addable = requires(T x) {
  x + x;
};
template <class T>
concept Multipliable = requires(T x) {
  x * x;
};
template <class T>
concept And = Addable<T> && Multipliable<T>;
template <class T>
concept Or = Addable<T> || Multipliable<T>;

auto f(And auto a) { std::cout << "A" << std::endl; }
auto f(Or auto a) { std::cout << "B" << std::endl; }

int main() { f(0); }


選択肢:
A
B
Complie Error
解答答えは A です.
conceptによる制約は一方が一方を包含するときはより強い制約が優先されます.本コードではBの制約がAの制約を包含しておりAの方が強い制約であるためAが呼ばれます.

問題9

以下のC++コードを動かすと何が出力されますか?

#include <iostream>

template <int x>
concept Range1 = -1 < x&& x < 1;
template <int x>
concept Range2 = -5 < x&& x < 5;

template <int a>
auto f() requires Range1<a> {
  std::cout << "A" << std::endl;
}
template <int a>
auto f() requires Range2<a> {
  std::cout << "B" << std::endl;
}

int main() { f<0>(); }


選択肢:
A
B
Complie Error
解答答えは Compile Error です.
前問と同じように考えると,より制約の強いAが呼ばれそうですがこれは違います.制約の強弱はconceptで定義された原子制約式自体による集合によって決まります.つまり,Range1は$\{x \in \mathbb{Z}| -1 < x < 1\}$という集合ではなく,$\textrm{Range1}$という集合になります.Aは$\textrm{Range1}$,Bは$\textrm{Range2}$で全く異なる集合であるため強弱は判断できず,どちらの関数も候補として残ります.よってコンパイルエラーです.

まとめ

問題名 解答 ジャンル
問題1 Compile Error 暗黙的な型変換
問題2 A 暗黙的な型変換
問題3 A コンストラク
問題4 A 可変長引数
問題5 B 可変引数テンプレート
問題6 Compile Error SFINAE
問題7 A concept
問題8 A concept
問題9 Compile Error concept

全問正解の方すごい!オーバーロードマスター!

7問以上正解の方勉強して再挑戦!

5問以上正解の方勉強して再挑戦!

3問以上正解の方勉強して再挑戦!

1問以上正解の方勉強して再挑戦!

全て不正解の方勉強して再挑戦!

おわりに

本記事ではC++オーバーロードに焦点をあてたクイズを展開しました.しかし,思った以上に仕様を読むのに苦戦してしまい,仕様書の提示が全く間に合ってないです.本記事はともかくとしてまた時間を取って読みたいと思います.
ただ,C++に限らず言語仕様をちゃんと理解するのは難しいししようがないね.仕様だけに.