C++1y exchange()関数

N3511 exchange() utility function


C++11ではアトミック操作のライブラリで、std::atomic_exchange()という関数が導入されました。この関数は、第1引数として受け取ったポインタが指す値を、第2引数の値で置き換え、戻り値として置き換え前の値を返します。

#include <iostream>
#include <atomic>

int main()
{
    std::atomic<int> x(3);
    int before = std::atomic_exchange(&x, 2);

    std::cout << before << std::endl;   // 変更前の値
    std::cout << x.load() << std::endl; // 変更後の値
}
3
2

この経験を踏まえて、非アトミックな値に対するexchange()関数を導入しよう、というのがこの提案です。

template<typename T, typename U>
T exchange(T& obj, U&& new_val) {
    T old_val = std::move(obj);
    obj = std::forward<U>(new_val);
    return old_val;
}

exchange()関数は、第1引数で受け取った変数への参照を、第2引数の値で置き換え、置き換え前の値を返します。


これは、いくつかの場面でプログラムを書きやすくします。以下は、コンテナをカンマ区切りで出力するプログラムです。まず、exchange()を使わない書き方は、以下のようになるでしょう。

#include <iostream>
#include <vector>

template <class T>
void print(const std::vector<T>& v)
{
    bool first = true;

    std::cout << '{';
    for (const T& x : v) {
        if (first) {
            first = false;
        }
        else {
            std::cout << ',';
        }
        std::cout << x;
    }
    std::cout << '}';
}

int main ()
{
    std::vector<int> v = {1, 2, 3};
    print(v);
}
{1,2,3}

カンマの数は、要素数 - 1である必要があるので、最初だけカンマ出力しないようにしています。フラグの反転とカンマの出力部分がやや冗長ですね。exchange()関数を使うと以下のように書けます。

#include <iostream>
#include <vector>

template<typename T, typename U>
T exchange(T& obj, U&& new_val) {
    T old_val = std::move(obj);
    obj = std::forward<U>(new_val);
    return old_val;
}

template <class T>
void print(const std::vector<T>& v)
{
    bool first = true;

    std::cout << '{';
    for (const T& x : v) {
        if (!exchange(first, false)) {
            std::cout << ',';
        }
        std::cout << x;
    }
    std::cout << '}';
}

int main ()
{
    std::vector<int> v = {1, 2, 3};
    print(v);
}
{1,2,3}

elseがなくなり、コードが少し短く済むようになりました。
その他、スマートポインタのリセット処理なども、exchange()関数を使うとすっきり書けるようになります。