valarray | Programming Place Plus C++編【標準ライブラリ】 第33章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


valarray

valarray は、数値演算に特化した一次元の配列を表現するクラステンプレートで、複数の要素に対して、同時に同じ操作(計算)を行うことを効率的に行えるように設計されています。

使用するには、<valarray> という名前の標準ヘッダをインクルードする必要があります。

valarray は、次のように定義されています。

namespace std {
    template <typename T>
    class valarray {
    };
}

テンプレート仮引数 T は、要素の型を指定します。valarray自体が、数値演算に特化したものなので、普通は数値型を指定することになるはずです。


生成と初期化

valarray には、複数のコンストラクタが定義されているので、さまざまな方法でインスタンス化できます。

valarray();
explicit valarray(std::size_t count);
valarray(const T& value, std::size_t count);
valarray(const T* values, std::size_t count);
valarray(const valarray& v);
valarray(const slice_array<T>& v);
valarray(const gslice_array<T>& v);
valarray(const mask_array<T>& v);
valarray(const indirect_array<T>& v);

1つ目の形式は、空の valarray を生成します。この場合、後から resizeメンバ関数を呼び出して、要素を割り当てます。

2つ目の形式は、指定した個数の要素を含んだ valarray を生成します。各要素は、デフォルトコンストラクタによって初期化されます。

3つ目の形式も、指定した個数の要素を含んだ valarray を生成しますが、各要素の初期値も指定します。vector などの STLコンテナにも、同じ意味合いのコンストラクタがありますが、引数の順番が逆になっていることに注意してください。

4つ目の形式は、指定した配列の要素を使って valarray を生成します。引数 values がヌルポインタでないことや、指し示す先に、引数count で指定した値以上の要素があることについては、プログラマー側で保証する必要があります。

5つ目の形式は、コピーコンストラクタです。

6つ目以降の形式は、valarray に関連する各種クラステンプレートから生成するためのものです。これについては、「サブセット」の項で取り上げます。

要素数

valarray は、内部に動的な一次元配列を持っています。この要素数を指して、valarray の要素数と呼ぶことにします。

valarray の現在の要素数は、sizeメンバ関数で取得できます。

std::size_t size() const;

valarray同士で何らかの操作を行う場合に、互いの要素数が一致していないと、未定義な動作になるので、要素数には注意を払ってください。また、当然ながら、[]演算子を使って要素にアクセスする際にも、要素数以上のインデックスを使用すると、未定義な動作になります

resizeメンバ関数を使うと、現在の要素数を変更できます。

void resize(std::size_t count, T value = T());

要素数が、第1引数で指定した値になります。

元の要素数よりも大きくなる場合は、新しい要素は第2引数の値で初期化されます。第2引数を指定しなかった場合は、デフォルトコンストラクタで初期化されます。

内部の配列自体が作り替えられる可能性があるため、要素を指すポインタや参照の類は無効になることに注意してください

演算子

valarray に対して使用できる演算子がいくつか定義されています。

[]演算子

valarray は一次元配列を模しているので、[]演算子を使って各要素にアクセスできます。有効範囲外のインデックスを指定すると、未定義な動作になります。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va(3);

    va[0] = 5.0;
    va[1] = 4.5;
    va[2] = -5.0;

    PrintValarray(va);
}

実行結果

5.00000, 4.50000, -5.00000

[]演算子は、サブセットを得る操作を行う際にも使用されます。これについては、「サブセット」の項で取り上げます。

単項演算子

以下の各種単項演算子を適用できます。

  • ~
  • !

valarray の全要素に対して、これらの演算子を適用し、結果の valarray を返します。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va(3);

    va[0] = 5.0;
    va[1] = 4.5;
    va[2] = -5.0;

    PrintValarray(-va);
}

実行結果

-5.00000, -4.50000, 5.00000

!演算子だけはやや特殊で、valarray の各要素に対して !演算子を適用した結果を、対応するインデックスに格納して返却します。そのため、!演算子の戻り値の型は valarray<bool> になります。

二項演算子と、その複合代入演算子

以下の各種二項演算子を適用できます。

  • *
  • /
  • %
  • &
  • ^
  • <<
  • >>

オペランドは、2つとも valarray にするか、一方を T型(T はテンプレート仮引数)の値にできます。

2つとも valarray にした場合は、互いの同じインデックスにある要素同士に対する演算になります。そのため、2つのオペランドを valarray にする場合は、両者の要素数が一致していないと未定義の動作になります。

一方を valarray、他方を T型の値にした場合は、valarray の全要素に対して、T型の値を使って演算子を適用します。たとえば、va という要素数が 3 の int型の valarray に対して「va * 2」とすることは、「va[0] * 2, va[1] * 2, va[2] * 2」とすることに等しいです。ただし、計算の順番は保証されません。

なお、これらの二項演算子に対応する、複合代入演算子も使用できます。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va1(5.0, 3);
    std::valarray<double> va2(2.0, 3);

    PrintValarray(va1 + va2);
    PrintValarray(va1 * 3.0);

    va1 -= va2;
    PrintValarray(va1);

    va2 -= 0.5;
    PrintValarray(va2);
}

実行結果

7.00000, 7.00000, 7.00000
15.0000, 15.0000, 15.0000
3.00000, 3.00000, 3.00000
1.50000, 1.50000, 1.50000

関係演算子、等価演算子、論理演算子

以下の関係演算子、等価演算子、論理演算子を適用できます。

  • <
  • >
  • <=
  • >=
  • ==
  • !=
  • &&
  • ||

二項演算子の場合と同じく、2つのオペランドは、ともに要素数が同じ valarray か、他方に T型の値を与えるかのいずれかになります。前者の場合は、互いの同じインデックスにある要素同士に対する演算になります。後者の場合は、全要素に対して、T型の値を使って演算子を適用します。

いずれの演算子も、戻り値の型は valarray<bool> になります。つまり、各要素に対する演算子の適用結果を、対応するインデックスに格納して返却します。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<bool>& va)
    {
        std::cout.setf(std::ios_base::boolalpha);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va1(3);
    va1[0] = 3.0;
    va1[1] = 4.0;
    va1[2] = 5.0;

    std::valarray<double> va2(3.0, 3);

    PrintValarray(va1 == va2);
    PrintValarray(va1 != va2);
    PrintValarray(va1 > va2);

    PrintValarray(va1 == 5.0);
    PrintValarray(va1 != 5.0);
    PrintValarray(va1 > 5.0);
}

実行結果

true, false, false
false, true, true
false, true, true
false, false, true
true, true, false
false, false, false

これらの演算子が返す結果には不満を感じる場面があるかもしれません。たとえば、2つの valarray の要素がすべて一致しているかどうかは、「va1 == va2」だけでは判定できず、返された valarray<bool> の要素がすべて true かどうかを調べる必要があります。そのため、補助関数を用意しておくと良いかもしれません。

#include <iostream>
#include <valarray>

namespace {
    bool IsAllTrue(const std::valarray<bool>& va)
    {
        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (!va[i]) {
                return false;
            }
        }
        return true;
    }
}

int main()
{
    std::valarray<double> va1(3);
    va1[0] = 3.0;
    va1[1] = 4.0;
    va1[2] = 5.0;

    std::valarray<double> va2(3.0, 3);

    std::cout << std::boolalpha
              << IsAllTrue(va1 == va1) << "\n"
              << IsAllTrue(va1 == va2) << "\n"
              << IsAllTrue(va2 == 3.0) << std::endl;
}

実行結果

true
false
true
C++11以降であれば、IsAllTrue関数は、次のように1行で書けます。
return std::all_of(std::cbegin(va), std::cend(va), [](bool b){return b;});


メンバ関数による操作

valarray には、すべての要素を対象にした操作を行うメンバ関数が用意されています。

最小値・最大値

minメンバ関数は、すべての要素の中で、最小の値を返します。 maxメンバ関数は、すべての要素の中で、最大の値を返します。

T min() const;
T max() const;

いずれも、valarray が要素を1つも持っていない場合には、未定義の動作になることに注意してください。また、minメンバ関数は大小関係の比較のために T型 に対する <演算子を、maxメンバ関数は >演算子を用います

合計

sumメンバ関数は、すべての要素の値の合計を返します。

T sum() const;

valarray が要素を1つも持っていない場合には、未定義の動作になることに注意してください。また、合計値を求めるために、T型に対する +=演算子を使用しています

シフト

shiftメンバ関数は、要素の位置を指定した個数分だけシフトさせます。

valarray<T> shift(int count) const;

引数 count の値が正数の場合は左シフト(配列の先頭に向かってシフト)になり、負数の場合は右シフト(配列の末尾に向かってシフト)になります。シフトした結果、空いた位置には、デフォルトコンストラクタによって初期化された新しい要素が作られます

constメンバ関数であることから分かるように、対象の valarray自身は変化しません。シフトした後の新しい valarray が戻り値で返されるので、これを受け取る必要があります。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va(5);
    va[0] = 3.0;
    va[1] = 4.0;
    va[2] = 5.0;
    va[3] = 6.0;
    va[4] = 7.0;

    PrintValarray(va);

    va = va.shift(2);
    PrintValarray(va);

    va = va.shift(-1);
    PrintValarray(va);
}

実行結果

3.00000, 4.00000, 5.00000, 6.00000, 7.00000
5.00000, 6.00000, 7.00000, 0.000000, 0.000000
0.000000, 5.00000, 6.00000, 7.00000, 0.000000

循環シフト

cshiftメンバ関数は、要素の位置を指定した個数分だけ循環シフトさせます。

valarray<T> cshift(int count) const;

引数 count の値が正数の場合は左シフト(配列の先頭に向かってシフト)になり、負数の場合は右シフト(配列の末尾に向かってシフト)になります。循環シフトなので、シフトによって配列の範囲外へ飛び出していった要素は、逆側から戻ってきます。そのため、要素に空きが生まれることはありません。

constメンバ関数であることから分かるように、対象の valarray自身は変化しません。シフトした後の新しい valarray が戻り値で返されるので、これを受け取る必要があります。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va(5);
    va[0] = 3.0;
    va[1] = 4.0;
    va[2] = 5.0;
    va[3] = 6.0;
    va[4] = 7.0;

    PrintValarray(va);

    va = va.cshift(2);
    PrintValarray(va);

    va = va.cshift(-1);
    PrintValarray(va);
}

実行結果

3.00000, 4.00000, 5.00000, 6.00000, 7.00000
5.00000, 6.00000, 7.00000, 3.00000, 4.00000
4.00000, 5.00000, 6.00000, 7.00000, 3.00000

関数呼び出しの適用

applyメンバ関数は、すべての要素に対して、指定の関数を適用します。

valarray<T> apply(T func(T)) const;
valarray<T> apply(T func(const T&)) const;

各要素を elem と表現すると、すべての要素に対して「func(elem)」を呼び出し、その結果を新しい valarray に格納します。最終的に、すべての結果を格納した valarray が戻り値で返されます。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va(5);
    va[0] = 3.7;
    va[1] = -1.9;
    va[2] = 2.5;
    va[3] = 0.6;
    va[4] = -4.0;

    PrintValarray(va.apply(std::floor));
    PrintValarray(va.apply(std::ceil));
    PrintValarray(va.apply(std::round));
}

実行結果

3.00000, -2.00000, 2.00000, 0.000000, -4.00000
4.00000, -1.00000, 3.00000, 1.00000, -4.00000
4.00000, -2.00000, 3.00000, 1.00000, -4.00000


数学系関数

以下の数学系関数が、引数と戻り値に valarray を取る形で定義されています。これらはメンバ関数ではなく、std名前空間内の通常の関数です。

template <typename T> valarray<T> abs(const valarray<T>& va);
template <typename T> valarray<T> exp(const valarray<T>& va);
template <typename T> valarray<T> log(const valarray<T>& va);
template <typename T> valarray<T> log10(const valarray<T>& va);
template <typename T> valarray<T> pow(const valarray<T>& base, const valarray<T>& exp);
template <typename T> valarray<T> pow(const valarray<T>& base, const T& vexp);
template <typename T> valarray<T> pow(const T& vbase, const valarray<T>& exp);
template <typename T> valarray<T> sqrt(const valarray<T>& va);
template <typename T> valarray<T> sin(const valarray<T>& va);
template <typename T> valarray<T> cos(const valarray<T>& va);
template <typename T> valarray<T> tan(const valarray<T>& va);
template <typename T> valarray<T> sinh(const valarray<T>& va);
template <typename T> valarray<T> cosh(const valarray<T>& va);
template <typename T> valarray<T> tanh(const valarray<T>& va);
template <typename T> valarray<T> asin(const valarray<T>& va);
template <typename T> valarray<T> acos(const valarray<T>& va);
template <typename T> valarray<T> atan(const valarray<T>& va);
template <typename T> valarray<T> atan2(const valarray<T>& y, const valarray<T>& x);
template <typename T> valarray<T> atan2(const valarray<T>& y, const T& vx);
template <typename T> valarray<T> atan2(const T& vy, const valarray<T>& x);

これらの関数は、C言語でも同名の関数があり、意味としては同じことなので、詳しい説明は省きます。

ここまでに説明してきたとおり、valarray同士で処理が行われるものは、同じインデックスの要素同士で、valarray と T型の値で処理が行われるものは、すべての要素に対して、1つの値を使って同じ処理が行われます。

なお、引数に2つの valarray を渡す場合は、要素数が同じでなければ未定義の動作になります

サブセット

valarray の要素のいくつかを選び出して、新たな valarray を得られます。たとえば、「1, 2, 3, 4, 5」という5つの要素で構成されている valarray から、「値が偶数の要素」という条件で、「2, 4」だけを含んだ valarray を簡単に得られるようになっています。

このように得られる新たな valarray をサブセットと呼びます。元になった valarray から要素が無くなるわけではありません。

サブセットを得るためにはいくつかの方法があるので、順番に説明していきます。

スライス

要素の開始位置、個数、間隔(ストライド)という3つの指定によって、サブセットを得る方法をスライスといいます。これを行うには、std::sliceクラスと、[]演算子を使用します。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<double>& va)
    {
        std::cout.setf(std::ios_base::showpoint);

        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<double> va1(5);
    std::valarray<double> va2(5);
    va1[0] = 0.0;
    va1[1] = 1.0;
    va1[2] = 2.0;
    va1[3] = 3.0;
    va1[4] = 4.0;
    va2[0] = 2.0;
    va2[1] = -1.0;
    va2[2] = -2.0;
    va2[3] = -1.0;
    va2[4] = 2.0;

    PrintValarray(va1[std::slice(0, 3, 2)]);
    PrintValarray(va1[std::slice(1, 2, 2)]);
    PrintValarray(va1[std::slice(4, 3, -2)]);

    va1[std::slice(0, 3, 2)] = 2.0;
    PrintValarray(va1);

    va1[std::slice(0, 3, 2)] *= va2[std::slice(0, 3, 2)];
    PrintValarray(va1);
}

実行結果

0.000000, 2.00000, 4.00000
1.00000, 3.00000
4.00000, 2.00000, 0.000000
2.00000, 1.00000, 2.00000, 3.00000, 2.00000
4.00000, 1.00000, -4.00000, 3.00000, 4.00000

std::slice の使い方が、関数呼び出しのように見えますが、これは一時オブジェクトを作って、コンストラクタに引数を渡しています。

第1引数が、サブセットに選び出す要素の開始インデックス、第2引数が、選び出す要素の個数、第3引数が、選び出す各要素間の距離です。これは負数にもできます。なお、こうして得られる対象要素のインデックスは、すべて有効なもので無ければなりません。有効でないインデックスを指定してしまうと、未定義の動作になります

[]演算子は、std::slice型を受け取れるようにオーバーロードされており、スライスによってサブセットを定義するように処理が行われます。

std::valarray<T>    operator[](std::slice slicearr) const;
std::slice_array<T> operator[](std::slice slicearr);

非constメンバ関数の場合は、std::slice_arrayクラステンプレートが返されます。これは、元の valarray への操作を行えるようにするための補助クラスです。主に代入系の演算子を使用でき、その演算は、元の valarray に対して行われます。

std::slice_array を経由して処理が行えない場合には、std::valarray へキャストしてください。std::valarray のコンストラクタには、std::slice_array を渡すタイプのものがあるので(「生成と初期化」の項参照)、static_cast を行うだけで変換できます。

汎用スライス

スライスをより汎用的にした汎用スライスという方法があります。汎用スライスは、サブセットをn次元の形で得られます。「n=1」であれば、前述のスライスと同様なので、こちらの方が汎用的であるという訳です。

汎用スライスでは、std::gsliceクラス[]演算子を使用します。汎用スライスの場合には、要素の開始位置、個数、間隔(ストライド)という3つの指定のうち、個数と間隔を、配列で指定することが可能になっています。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<int>& va)
    {
        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<int> va(25);
    for (std::size_t i = 0; i < va.size(); ++i) {
        va[i] = i;
    }

    std::size_t numsTable[] = {5, 2};
    std::size_t strideTable[] = {5, 1};
    std::valarray<std::size_t> num(numsTable, 2);
    std::valarray<std::size_t> stride(strideTable, 2);

    PrintValarray(va[std::gslice(1, num, stride)]);
}

実行結果

1, 2, 6, 7, 11, 12, 16, 17, 21, 22

最初に、5x5 の2次元配列をイメージして、valarray を定義し、ここから、5x2 の範囲の2次元配列を、サブセットとして得ようとしています。

スライスの場合と同じで、今度は std::gslice を []演算子のオペランドとして指定します。std::gslice の第2、第3引数は、valarray<std::size_t>型で指定します。

【上級】valarray<std::size_t>型で指定するために、valarray<std::size_t>型の変数を定義し、そこに渡す値のために、std::size_t型の配列を定義するという、かなり面倒なコードになっています。C++11以降であれば、valarray のコンストラクタに std::initializer_list を指定できるので、直接、「va[std::gslice(1, {5, 2}, {5, 1})]」のような記述が可能です。これなら煩わしくないし、見やすいですね。

[]演算子は、std::gslice型を受け取れるようにオーバーロードされており、汎用スライスによってサブセットを定義するように処理が行われます。

std::valarray<T>     operator[](std::gslice gslicearr) const;
std::gslice_array<T> operator[](std::gslice gslicearr);

非constメンバ関数の場合は、std::gslice_arrayクラステンプレートが返されます。これは、元の valarray への操作を行えるようにするための補助クラスです。主に代入系の演算子を使用でき、その演算は、元の valarray に対して行われます。

std::gslice_array を経由して処理が行えない場合には、std::valarray へキャストしてください。std::valarray のコンストラクタには、std::gslice_array を渡すタイプのものがあるので(「生成と初期化」の項参照)、static_cast を行うだけで変換できます。

マスク

マスクは、valarray<bool> を使って、サブセットを定義する方法です。true が入っている箇所に対応する要素だけが選び出されます。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<int>& va)
    {
        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<int> va(25);
    for (std::size_t i = 0; i < va.size(); ++i) {
        va[i] = i;
    }

    PrintValarray(va[va < 10]);
    PrintValarray(va[va % 3 == 0]);
    PrintValarray(va[5 <= va && va <= 15]);
}

実行結果

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 3, 6, 9, 12, 15, 18, 21, 24
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15

valarray<bool> は、valarray に対して関係演算子や等価演算子、論理演算子を適用すると得られる型と一致しています(「演算子」の項を参照)。そのため、「va[va < 10]」のような感じで記述できます。

[]演算子は、valarray<bool>型を受け取れるようにオーバーロードされており、マスクによってサブセットを定義するように処理が行われます。

std::valarray<T>   operator[](valarray<bool>& va) const;
std::mask_array<T> operator[](valarray<bool>& va);

非constメンバ関数の場合は、std::mask_arrayクラステンプレートが返されます。これは、元の valarray への操作を行えるようにするための補助クラスです。主に代入系の演算子を使用でき、その演算は、元の valarray に対して行われます。

std::mask_array を経由して処理が行えない場合には、std::valarray へキャストしてください。std::valarray のコンストラクタには、std::mask_array を渡すタイプのものがあるので(「生成と初期化」の項参照)、static_cast を行うだけで変換できます。

間接的な指定

valarray<std::size_t> を使って、サブセットを定義する方法もあります。valarray<std::size_t> には、サブセットとして選び出したい要素のインデックスを格納しておきます。この方法は、最後に登場する indirect_array というクラステンプレートに倣って、間接的な指定と呼びます。

#include <iostream>
#include <valarray>

namespace {
    void PrintValarray(const std::valarray<int>& va)
    {
        const std::size_t size = va.size();
        for (std::size_t i = 0; i < size; ++i) {
            if (i >= 1) {
                std::cout << ", ";
            }
            std::cout << va[i];
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::valarray<int> va(25);
    for (std::size_t i = 0; i < va.size(); ++i) {
        va[i] = i;
    }

    std::valarray<std::size_t> indexes(5);
    indexes[0] = 7;
    indexes[1] = 15;
    indexes[2] = 7;
    indexes[3] = 10;
    indexes[4] = 4;

    PrintValarray(va[indexes]);
}

実行結果

7, 15, 7, 10, 4

インデックスを格納した std::valarray<std::size_t> には、同じインデックスが含まれても構いませんし、大小関係がどういう順序で並んでいても構いません。ただし、無効なインデックスが入っていると、動作は未定義になります。

[]演算子は、valarray<std::size_t>型を受け取れるようにオーバーロードされており、間接的な指定によってサブセットを定義するように処理が行われます。

std::valarray<T>       operator[](valarray<std::size_t>& va) const;
std::indirect_array<T> operator[](valarray<std::size_t>& va);

非constメンバ関数の場合は、std::indirect_arrayクラステンプレートが返されます。これは、元の valarray への操作を行えるようにするための補助クラスです。主に代入系の演算子を使用でき、その演算は、元の valarray に対して行われます。

std::indirect_array を経由して処理が行えない場合には、std::valarray へキャストしてください。std::valarray のコンストラクタには、std::mask_array を渡すタイプのものがあるので(「生成と初期化」の項参照)、static_cast を行うだけで変換できます。


練習問題

問題① valarray に含まれている要素の平均値を求めるプログラムを作成してください。

問題② 2つの valarray の内積を求めるプログラムを作成してください。

問題③ 次のコードで作られる valarray を 5x5 の2次元配列(あるいは行列)と見なしたとき、左上から右下へ向かう対角線上の要素を、サブセットとして選び出すプログラムを、スライス、汎用スライス、マスク、間接的な指定の各方法で作成してください。

std::valarray<int> va(25);
for (std::size_t i = 0; i < va.size(); ++i) {
    va[i] = i;
}


解答ページはこちら

参考リンク


更新履歴

’2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

’2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

’2017/3/25 VisualC++ 2017 に対応。

’2017/2/28 新規作成。



前の章へ (第32章 アロケータ)

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る