trailing comma問題

別名ケツカンマ問題。

最後の要素の後にはカンマを入れてはいけない、という場合にどう対処するか。

要素のインデックスやイテレータを使ってループしているなら、普通にそれを見ればいい。

std::cout << "[";
for(std::size_t i=0; i<v.size(); ++i)
{
    std::cout << to_string(v.at(i));
    if(i + 1 != v.size())
    {
        std::cout << ", ";
    }
}
std::cout << "]";

とはいえ、インデックスはtypoしやすいし、インデックスの値そのものが必要でないならrange based for loopを使いたい。 だが、range based for loopでは今が先頭かどうか判定する方法がないので、フラグを外に用意しないといけない。

std::cout << "[";
{
    bool is_front = true;
    for(const auto& elem : v)
    {
        if(not is_front)
        {
            std::cout << ", ";
        }
        std::cout << to_string(elem);
        is_front = false;
    }
}
std::cout << "]";

is_frontのようなフラグが漏れ出してしまうのを防ぐには、スコープを導入しなければならない。とはいえこれは不恰好だ。

C++20ではforで初期化式が使えるようになるので、この外のスコープが不要になり少しだけ簡潔になる。

std::cout << "[";
for(bool is_front = true; const auto& elem : v)
{
    if(not is_front)
    {
        std::cout << ", ";
    }
    std::cout << to_string(elem);
    is_front = false;
}
std::cout << "]";

だが、直接coutに書き出すのでなく文字列を用意して良いのなら、そして最初と最後に括弧が入るなら、C++11の範囲内ですらループ内から分岐を追い払うことが可能になる。最後のコンマと最初のスペースを、ループが終わったあとで括弧に置き換えれば良い。そのために、値のあとで", "を書き込んでいたのを、,に分解して前後に持ってくる。

std::string str;
for(const auto& elem : v)
{
    str += ' ';
    str += to_string(elem);
    str += ',';
}
if(v.empty()){str.resize(2);}
str.front() = '[';
str.back()  = ']';
std::cout << str;

frontbackは境界チェックをしないので、空だった時は別に領域を確保しておかないといけないことに気をつけよう。

埋めるべき括弧がフォーマット上存在しない時も、pop_backを使って最後のコンマをあとで消すこともできる。pop_backなら定数時間だ。

ループ内から分岐をなくせたのはよかったが、コードを見てみると思ったほどシンプルにはならなかったな。