コピペすると(たぶん)どこでvectorを範囲外参照しているかがわかるソースコード(を作りたい)

※未定義動作です!!!
AtCoderのコードテストでは自分が試した範囲ではそれっぽく動きましたがわかりません…
あと健全な(未定義動作でない)使い方案も一応あります
(以下のすべての文はうまく動くということにして落書き程度に読んでください)



(自己責任で)適当にコピペ・改変してください
改善案募集中です!!!!!!!!!!!!!!

使い方(通常版)

C++20以降、もしくはC++17以降&boost(1.74以降で動くが1.79以降推奨)が必要です。
下のソースコードをincludeの後かつvectorが出てくる前に貼ってください。(includeの前に貼ってもたぶん動くようになっていると思いますが非推奨です)
includeは適当に省いてください。boost/assert/source_location.hpp以外はbits/stdc++.hに含まれています。
先に調べるソースコードコンパイルできるか確認してから貼ること、提出時は消すことを推奨します。

C++20版

#include <iostream>
#include <vector>
#include <source_location>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        subscript_and_location(int sub_,std::source_location loc_=std::source_location::current()){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging

boost版

#include <iostream>
#include <vector>
#include <boost/assert/source_location.hpp>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        boost::source_location loc;
        subscript_and_location(int sub_,boost::source_location loc_=BOOST_CURRENT_LOCATION){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging

貼ってコンパイル・実行すると、vectorを範囲外参照していた場合

ファイル名:(行:列):関数名
out of range: subscript = 添字, vector_size = vectorのサイズ

標準エラー出力に出てきてプログラムが異常終了(RE)します。
(boost版を使っている&boost1.79より前の場合多分関数名がうまく表示されません)

使用例

#include <bits/stdc++.h>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        subscript_and_location(int sub_,std::source_location loc_=std::source_location::current()){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging
using namespace std;
int main(){
    vector<int> v(3,0);
    cout << v[5] << endl;
}

AtCoderのコードテスト上でコンパイル・実行すると

Main.cpp:(40:16):int main()
out of range: subscript = 5, vector_size = 3

標準エラー出力に出てきて異常終了します。

長所・短所

長所

  • 貼るだけで範囲外参照を調べることができるので、デバッガを使わなくて済み楽
  • .at()と異なり、何行何列で配列外参照したかがわかる
  • .at()と異なり、もし添字がマイナスでもマイナスのままエラーに出てくる
  • 一応消し忘れても提出できる

短所

  • 未定義動作(#define vector ○○している、std名前空間にプログラムを追記している*1 )
  • 範囲外参照以外の実行時エラーがわからない
  • 消し忘れた場合実行時間が少し長くなる
  • 長い

短所が短所すぎる…

何してるの?

基本はstd::vector::operator[]に範囲チェックを追加するヘッダ - 簡潔なQと同じです。これ賢いですよね…
ここに継承コンストラクタを付けて、添字が範囲内かどうかチェックをして戻り値を[]演算子にしたものがこれです:

コード

#include <bits/stdc++.h>
using namespace std;
namespace std{
    template<class T> class vetor_for_dubugging:public vector<T>{
        using vector<T>::vector;
        public:
            T& operator[](int n){
                if(n<0||(int)this->size()<=n){
                    //範囲外参照
                    exit(EXIT_FAILURE);
                }
                return vector<T>::operator[](n);
            }
    };
}
#define vector vetor_for_dubugging

これだとどこで範囲外参照したかがわからないので、source_locationを使って[]演算子を呼び出した場所を持っておきます。
[]演算子のデフォルト引数は禁止されていて困った~~~と思っていたらc++ - operator[] caller's site source location current workaround - Stack Overflowを発見して、これを読むと構造体やクラスを引数にしてそのコンストラクタのデフォルト引数を使えばいいことが分かり賢いな~~~と思いました。
これを実装するとこうなります:
コード

#include <bits/stdc++.h>
using namespace std;
struct subscript_and_location{
    int sub;
    source_location loc;
    subscript_and_location(int sub_,source_location loc_=source_location::current()){
        sub=sub_;
        loc=loc_;
    }
};
namespace std{
    template<class T> class vetor_for_dubugging:public vector<T>{
        using vector<T>::vector;
        public:
            T& operator[](subscript_and_location n){
                if(n.sub<0||(int)this->size()<=n.sub){
                    //範囲外参照
                    clog << n.loc.file_name() << ":(" << n.loc.line() << ":" << n.loc.column() << "):" << n.loc.function_name() << endl;
                    clog << "out of range: subscript = " << n.sub << ", vector_size = " << this->size() << endl;
                    exit(EXIT_FAILURE);
                }
                return vector<T>::operator[](n.sub);
            }
    };
}
#define vector vetor_for_dubugging

後は貼った下でincludeしても大丈夫なように頑張ってstl_vector.hとかstl_bvector.hを読みながらいい感じにしています。
vector<bool>の[]演算子にはnoexceptが付いていなかったのでなんとなく場合分けしています。(なので場合分けを消しても動きます というか[[nodiscard]]とconstexprとnoexceptは消しても動きます)

健全な(未定義動作でない)使い方案

案1 デバッグ用のvectorをstd名前空間の外にすべて移して、普段はstd::vectorを使い、デバッグをしたいときだけすべてデバッグ用のvectorに書き換える

#include <iostream>
#include <vector>
#include <source_location>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        subscript_and_location(int sub_,std::source_location loc_=std::source_location::current()){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
    template<class T,class Allocator=std::allocator<T>> class vector:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
}
int main(){
    //デバッグ時はstd::vectorをfor_debugging::vectorに書き換える
}

案2 デバッグ用のvectorをstd名前空間の外にすべて移して、普段はstd::vectorを別の名前でusing宣言したものを使い、デバッグしたい時だけusing宣言を書き換える

#include <iostream>
#include <vector>
#include <source_location>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        subscript_and_location(int sub_,std::source_location loc_=std::source_location::current()){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
    template<class T,class Allocator=std::allocator<T>> class vector:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
}
template<class T,class Allocator=std::allocator<T>> using vec=std::vector<T,Allocator>;
//template<class T,class Allocator=std::allocator<T>> using vec=for_debugging::vector<T,Allocator>;  //デバッグ時だけこっちを使う
int main(){
    //vecをvectorとして使う
}

案3 デバッグ用のvectorをstd名前空間の外にすべて移して、デバッグ時はstd::vectorを使用するすべての関数や名前空間の先頭にusing宣言を書く(using namespace std;をしてvectorをstd::を付けずにstd::vectorを使っていないとうまくいきません)

#include <iostream>
#include <vector>
#include <source_location>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        subscript_and_location(int sub_,std::source_location loc_=std::source_location::current()){
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
    template<class T,class Allocator=std::allocator<T>> class vector:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
}
using namespace std;
int main(){
    //using for_debugging::vector;  //デバッグ時はvectorを使用する関数や名前空間の先頭にこれを書く
}

案4 使わない

おまけ1:簡易版

  • 上でusing namespace std;などをしていてvectorをstd::を付けずに使っている(std::を付けていてもstd名前空間の中に書けば大丈夫です)
  • 下でincludeしてはいけない
  • vectorが使えない
  • 戻り値を破棄していても警告が出ない
  • 自作アロケータが使えない

などの制限が付く代わりに短いバージョンです。
ほとんど『何してるの?』の2つ目のソースコードと変わりません

C++20版

#include <iostream>
#include <vector>
#include <source_location>
using namespace std;
struct sub_loc{
    int sub;
    source_location loc;
    sub_loc(int sub_,source_location loc_=source_location::current()){
        sub=sub_;
        loc=loc_;
    }
};
template<class T> struct vfd:public vector<T>{
    using vector<T>::vector;
    T& operator[](sub_loc n){
        if(n.sub<0||(int)this->size()<=n.sub){
            clog << n.loc.file_name() << ":(" << n.loc.line() << ":" << n.loc.column() << "):" << n.loc.function_name() << endl;
            clog << "out of range: subscript = " << n.sub << ", vector_size = " << this->size() << endl;
            exit(EXIT_FAILURE);
        }
        return vector<T>::operator[](n.sub);
    }
};
#define vector vfd

boost版

#include <iostream>
#include <vector>
#include <boost/assert/source_location.hpp>
using namespace std;
struct sub_loc{
    int sub;
    boost::source_location loc;
    sub_loc(int sub_,boost::source_location loc_=BOOST_CURRENT_LOCATION){
        sub=sub_;
        loc=loc_;
    }
};
template<class T> struct vfd:public vector<T>{
    using vector<T>::vector;
    T& operator[](sub_loc n){
        if(n.sub<0||(int)this->size()<=n.sub){
            clog << n.loc.file_name() << ":(" << n.loc.line() << ":" << n.loc.column() << "):" << n.loc.function_name() << endl;
            clog << "out of range: subscript = " << n.sub << ", vector_size = " << this->size() << endl;
            exit(EXIT_FAILURE);
        }
        return vector<T>::operator[](n.sub);
    }
};
#define vector vfd

おまけ2:添字の型チェック付き版

添字が整数型かどうか判定する機能がおまけで付いてくる版です。
先に調べるソースコードコンパイルできるか確認してから貼ることを推奨します。
後は通常版と同じです。

C++20版

#include <iostream>
#include <vector>
#include <source_location>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        template<class T> subscript_and_location(T sub_,std::source_location loc_=std::source_location::current()){
            if(!std::is_integral<T>::value){
                std::clog << loc_.file_name() << ":(" << loc_.line() << ":" << loc_.column() << "):" << loc_.function_name() << std::endl;
                std::clog << "subscript is not integer: subscript = " << sub_ << std::endl;
                exit(EXIT_FAILURE);
            }
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging

boost版

#include <iostream>
#include <vector>
#include <boost/assert/source_location.hpp>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        boost::source_location loc;
        template<class T> subscript_and_location(T sub_,boost::source_location loc_=BOOST_CURRENT_LOCATION){
            if(!std::is_integral<T>::value){
                std::clog << loc_.file_name() << ":(" << loc_.line() << ":" << loc_.column() << "):" << loc_.function_name() << std::endl;
                std::clog << "subscript is not integer: subscript = " << sub_ << std::endl;
                exit(EXIT_FAILURE);
            }
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging

貼ってコンパイル・実行すると、通常版の機能に加えて、添字が整数型ではなかった場合

ファイル名:(行:列):関数名
subscript is not integer: subscript = 添字

標準エラー出力に出てきてプログラムが異常終了(RE)します。
桁数などは指定していないため必要であれば適当にstd::clog << "subscript is not integer~の部分を改変してください(すみません)

使用例

#include <bits/stdc++.h>
namespace for_debugging{
    struct subscript_and_location{
        int sub;
        std::source_location loc;
        template<class T> subscript_and_location(T sub_,std::source_location loc_=std::source_location::current()){
            if(!std::is_integral<T>::value){
                std::clog << loc_.file_name() << ":(" << loc_.line() << ":" << loc_.column() << "):" << loc_.function_name() << std::endl;
                std::clog << "subscript is not integer: subscript = " << sub_ << std::endl;
                exit(EXIT_FAILURE);
            }
            sub=sub_;
            loc=loc_;
        }
        void check_out_of_range(size_t sz){
            if(sub<0||(int)sz<=sub){
                std::clog << loc.file_name() << ":(" << loc.line() << ":" << loc.column() << "):" << loc.function_name() << std::endl;
                std::clog << "out of range: subscript = " << sub << ", vector_size = " << sz << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    };
}
namespace std{
    template<class T,class Allocator=std::allocator<T>> class vector_for_debugging:public std::vector<T,Allocator>{
        using std::vector<T,Allocator>::vector;
        public:
            [[nodiscard]] constexpr std::vector<T,Allocator>::reference operator[](for_debugging::subscript_and_location n) noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
            [[nodiscard]] constexpr std::vector<T,Allocator>::const_reference operator[](for_debugging::subscript_and_location n) const noexcept(!std::is_same<T,bool>::value){
                n.check_out_of_range(this->size());
                return std::vector<T,Allocator>::operator[](n.sub);
            }
    };
    namespace pmr{
        template<class T> using vector_for_debugging=std::vector_for_debugging<T,std::pmr::polymorphic_allocator<T>>;
    }
}
#define vector vector_for_debugging
using namespace std;
int main(){
    vector<int> v(3,0);
    cout << v[1.4] << endl;
}

AtCoderのコードテスト上でコンパイル・実行すると

Main.cpp:(45:18):int main()
subscript is not integer: subscript = 1.4

標準エラー出力に出てきて異常終了します。

雑談

無限時間溶かしたミス集:

  • テンプレートの部分特殊化にも一次テンプレートのデフォルトテンプレート引数が引き継がれる

例:

template<class T,class U=int> class c{
};
template<class U> class c<bool,U>{
};

これをc<bool>で使うとUがintになる

  • typedef宣言も継承できる

  • #defne vector ○○は未定義

みんなも無限時間溶かさないように気を付けよう!

*1:えびちゃんさんからご指摘をいただきました(https://twitter.com/rsk0315_h4x/status/1727637308336435238) ありがとうございます!