タイトル通りの記事なので、#define int long longアレルギーの方はそっ閉じしてください。
Twitter(X)上で#define int long longについて悩んでいる人を見かけたので、プロ#define int long longユーザーの私がちょっとだけ解説してみようと思います。
そもそもなぜ#deifne int long longしようと思うのか
多くの場合、intは4byte、long longは8byteなので、本来#define int long longは必要ないならばしたくないものです。
CPUのキャッシュは有限でかなり小さいので倍の容量が必要になるとメモリアクセスがボトルネックになって実行時間が遅くなることは十分にあり得ますし、現在(2024/10/20)のAtCoderではあまり遭遇しませんが問題のメモリ制限に引っかかってMLEになる可能性だってあります。
では、long longが必要な場合はどのようなときでしょうか。
単純な例では、入力long longが必要になります。
競プロ初心者がたまに引っかかるものは、intだとオーバーフローしてしまうことや、
問題の解法は合っているのにオーバーフローでペナを食らったり、コンテスト終了までにオーバーフローに気づかずACできなかったときなんかは、後で気づいた時に悔しくて発狂しますよね。
そういう経験をしたポンコツな私のような皆さんはその次にこう思うのです。
#define int long longしてればこんなことにならなかったのに……と。
#define int long longのデメリット
まぁデメリットがあろうが何だろうがACできないよりはマシだと思えば、発狂経験のある諸氏は納得していただけると思うが、それでも書かない訳にはいかないので書きます。
メモリを多く消費する
何よりも当たり前の事実としてメモリを食います。
前述の通り、キャッシュミスヒットで効率が下がることもあれば、MLEになる可能正だって(AtCoderでは低いけど)あります。
ただ、問題の正しい解法を思いついていた場合は高々メモリ使用量が2倍になったくらいでTLEやMLEになることは無いと信じてもいいと思います。
(そもそもメモリ使用量が2倍になった程度でTLEするんだったら定数倍高速化の前に解法の計算量が大きすぎる可能性のほうが高いので)
32bit整数を使いにくい
当たり前ですが、intと書いて64bitになるなら32bitの整数の型の名前をちゃんと思い出しておかないといけません。signedとunsignedです。こいつらはintを省略してもいいよという雑な取り決めのおかげで#define int long longしても32bitのまま使えます。
一つ注意点として、今までint main()としていたところはsigned main()にしなければなりません。unsignedはコンパイルエラーになります。
ライブラリのコンパイルが通らない
コンパイルエラーのコードの読み方がわからないとなぜかライブラリを使ったときにエラーが出てどうしようもないことになります。コンパイルエラーが出るときは、ライブラリの中でint(~~~~)のようにintのコンストラクタが呼び出されているなどの場合や、C++20以降で導入されたconceptsでrequires(sizeof(T)==4)のように型のサイズを固定している場合などです。コンパイルエラーが出ないエラーがあるとさらに面倒で、例えば最上位bitの場所を教えてくれる関数はcountl_zero<_TP>()なので、bit数が違うと頭のゼロの数が変わって動作が狂います。
しかし、これは#define int long longをすべての#includeより下に書くことで解決できます。私はテンプレを別ファイルにして提出時には#include <local.h>をテンプレファイルで置き換えるプログラムでクリップボードにコピーしていますが、そのような場合でも#include <local.h>の上の行に新たに必要なヘッダを#includeすることで問題なく動作します。
using ll = long long;ではダメなのか
ダメではないです。ですが、私たちはなぜ#define int long longしたいのですか?
うっかりオーバーフローするのを未然に防ぎたいからです。
必要なときにllを使うのであればうっかりは防げません。
では、すべての整数をllで扱えばいいのではないか。はい、その通りです。
llのほうが文字数も少ないし、intとの区別もできていいじゃないか。はい、その通りです。
しかし、一つ問題点を上げるとするとllは右手薬指二連打で少し打ちにくいのです。
コメントで指摘してもらいましたが、ll以外に打ちやすいエイリアスを用いれば連打力は必要なくなります。
なので、もうちょっと#define int long long擁護派として追記しておくと、新しいエイリアスを作ると、短い変数名と衝突しやすいという問題が発生します。
llなら二つl,rを管理するような場面でllが使えなくなりますし、iiなら4重ループでループ変数をi,j,ii,jjとできなくなります。
もちろん探せば衝突しない良いエイリアスがあるのかもしれませんが、探すのめんどくさいコンテスト中に変数名を考えるのに時間を消費するなんて時間の無駄じゃないですか(建前)。
でも、ちゃんと変数名を考える人はusingでエイリアスするかlong使うかでいいと思います。
というか、windows環境でプログラミングしてないならlongでいいと思います。
追記終わり
ここで、私のもう一つのおすすめも紹介しておきます。
#define itn long longです。
これも追加しておくとintをitnと間違えてしまっても何も問題なく動きます。
intはすべて違う指で押すのでタイピングはこちらの方が前後も合わせて早く打ちやすいです。
特にこだわりが無いのであればusing ll = long long;のほうが良いと思います。
補足 int以外もエイリアスしよう
C/C++では環境によってint、long、long longなどの型のサイズは違います。
規格で定められているのは決められた文字をすべて収めるのに十分なcharのサイズを1byteとして、それの整数倍の大きさでcharshortintlonglong longの大小関係を持つことだけだったはずなので、intとlongが同じ大きさだったりlongとlong longが同じ大きさだったりします。
Unix系のOSでは多くがLP64という規格で、longが64bitです。AtCoderもこの大きさです。
なので本当は#define int long longではなく#define int longでいいのです。
例えば、ローカルの環境がWindowsならばlong longでないと32bitになってしまうこともあると思います。
(無いとは思いますがまだ32bitのWin7とかを使ってるならlong longじゃないといけません)
なので、すべての整数型を明示的にbit数がわかる形で使った方がいいです。
しかし、そのような型は名前が長くて打ちにくいのでusingして楽をしようというのがここの趣旨です。
#include <cstdint>
using i8 = int8_t;
using u8 = uint8_t;
using i16 = int16_t;
using u16 = uint16_t;
using i32 = int32_t;
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;
using intw = __int128_t;
using uintw = __uint128_t;
#define int long long
#define itn long long
余計なものが混じっているように見えますが、これも必要なんです。ワイドなintでintwですが、これは64bitでもオーバーフローするときに使います。私はオーバーフロー対策なんてできないので、__int128_tでゴリ押します。異論は認めません。例えばどの問題で使うのかと言うと、典型90問-082 - Counting Numbers(★3)です。dで持つとします。この時制約でlong longではオーバーフローしますが、unsigned long longではオーバーフローしません。でもそんな細かい数字覚えてられませんよね?そんなあなたに__int128_tです。確かにこの問題では必要ないかもしれませんが、あったら考えることが一つ減ります。intwで宣言してしまえばいいのです。
ただ、__int128_tと__uint128_tはstd::cin,std::coutで入出力できないので、演算子のオーバーロードを定義する必要があります。
以下に私が使ってるテンプレの、一部をエイリアスをできるだけ除いて置いておきます。
使う場合はグローバルに貼り付けてください。バッファに使ってるstatic char _O128B[128];がグローバル変数なので……。
// C++20?(もしかしたら23かも)
// #include <bits/stdc++.h> か以下のヘッダを入れる
// #include <concepts> // concept
// #include <type_traits> // is_integral_v<>
// #include <limits> // numeric_limits<>::is_signed
// #include <iterator> // end()
// #include <cstddef> // size_t
// #include <ranges> // views::drop()
// #include <utility> // pair<>
// #include <cassert> // assert()
// #include <fstream> // istream,ostream
// #include <iostream> // cin,cout
// using namespace std;
#define template _TP // 許せ
#define uintw __uint128_t
_TP<class T> concept Lint = is_integral_v<T> && sizeof(T)>8;
static char _O128B[128];
pair<char*, ssize_t> _O128(uintw tmp) {
char*d=end(_O128B);
do {
*(--d) = "0123456789"[tmp % 10];
tmp /= 10;
} while (tmp != 0);
return {d,end(_O128B) - d};
}
_TP<Lint T>
ostream &operator<<(ostream &dst, T val) {
ostream::sentry s(dst);
if (s) {
auto [d, len] = _O128(val < 0 ? -val : val);
if (val < 0)
*(--d) = '-', len++;
if (dst.rdbuf()->sputn(d, len) != len)
dst.setstate(ios_base::badbit);
}
return dst;
}
_TP<Lint T>
istream &operator>>(istream &src, T &val) {
str s;src>>s;
bool is_neg=numeric_limits<T>::is_signed&&s.size()>0&&s[0]=='-';
for(val=0;const auto&x:s|views::drop(is_neg)){
// assert('0'<=x&&x<='9');
val=10*val+x-'0';
}
if (is_neg) val*=-1;
return src;
}
Comments
Let's comment your feelings that are more than good