std::extent の改善

記事「std::extent で配列サイズを取得」にて std::extent の使い方を紹介しました。
std::extent にはやや意外な挙動が含まれているので、その注意点と回避方法について説明します

std::extent は配列のサイズを求める良い手段ですが、指定した型が非配列である時には、std::extent<>::value の値が 0 になるという動作になります。
以下の例は、std::extent の引数に誤ってポインタを渡してしまう例です。ポインタは配列ではないので、std::extent<>::value の値は 0 になります。
#include <iostream>
#include <type_traits>

int main(void)
{
    int a[3] = {1, 2, 3};
    int* p = a

    // std::enxtentを使用
    // std::extent<decltype(p)>::valueは 0 である
    for (size_t i = 0; i < std::extent<decltype(p)>::value; i++) {
        std::cout << a[i] << " "; // => for文に入らない
    }

    // 参考までに、C言語以来のクラシカルなやり方
    // (sizeof(p) / sizeof(p[0]))は環境によって 2 などになる
    // 条件によっては不定動作につながってしまう
    for (size_t i = 0; i < (sizeof(p) / sizeof(p[0])); i++) {
        std::cout << a[i] << " "; // => "1 2 "
    }

    return 0;
}
上の例ではfor文の中に入らないので不定動作とはなりませんが、ここは p が配列でないことをコンパイルエラーで知らせてくれる方がありがたいです。

テンプレート引数が配列でない場合にはコンパイルエラーを起こす std::extent のラッパークラスの実装例を、以下に記します。
#include <iostream>
#include <type_traits>

// std::extentのラッパークラス
// テンプレート引数が配列でなければ、static_assertが起こる
template <typename ARRAY>
struct array_size : std::extent<ARRAY>
{
    static_assert(std::is_array<ARRAY>::value, "argument of array_size must be array");
};

int main(void)
{
    int a[3] = {1, 2, 3};
    int *p = a;

    // 配列に対して使用。std::enxtentと同じ動作
    for (size_t i = 0; i < array_size<decltype(a)>::value; i++) {
        std::cout << a[i] << " "; // => "1 2 3 "
    }

    // ポインタに対して使用。コンパイルエラー!
    for (size_t i = 0; i < array_size<decltype(p)>::value; i++) {
        std::cout << a[i] << " ";
    }

    return 0;
}

0 件のコメント:

コメントを投稿