配列パラメータの扱いにおけるC++での注意点

C++において、関数の引数として配列を渡す際に重要なポイントがあります。特に、const char str[N]を使用すると予期しない問題が発生することがあります。この記事では、その問題と解決策について詳しく説明します。

問題点: 配列の退化

まず、const char str[N]という形で関数に配列を渡すと、コンパイラはこれをconst char*に変換してしまいます。この現象を「配列の退化」と呼びます。これにより、配列の長さ情報が失われ、テンプレートパラメータであるNも推論できなくなります。

正しい方法: 配列参照を使う

配列の長さ情報を保持し、型安全性を確保するためには、const char (&str)[N]という形式を使用する必要があります。この書き方により、配列の長さ情報が保持され、テンプレートパラメータの推論も可能になります。

背景: 配列退化の仕組み

C++の規格では、配列が関数の引数や代入文に現れる場合、暗黙的にその先頭要素へのポインタに変換されます。このルールはC言語から引き継がれており、以下の場合を除いて適用されます:

  • アドレス取得演算子(&)
  • 配列添字演算子([])
  • sizeof演算子
  • typeid演算子

このため、const char str[N]のように書いた場合、実際にはconst char* strと同じ意味になり、Nは無視されます。

解決策: 強い型チェックによる退化防止

参照(&)を使うことで、この退化を防ぐことができます。参照は既存オブジェクトの別名であり、型チェックが厳密に行われるため、配列の完全な型(要素の型と長さ)を保持できます。

例: 退化を確認するコード


#include <iostream>
using namespace std;

// 配列が退化する例
void test(const char str[100]) {
    cout << typeid(decltype(str)).name() << endl; // 出力: const char*
}

int main() {
    const char arr[5] = "test";
    test(arr); // 配列arrがconst char*に退化
    return 0;
}

例: 参照で長さを保持するコード


#include <iostream>
using namespace std;

// 配列参照を使用する例
template<size_t N>
void test(const char (&str)[N]) {
    cout << typeid(decltype(str)).name() << endl; // 出力: const char [6]
    cout << "N = " << N << endl; // 出力: 6
}

int main() {
    test("hello"); // const char[6]として認識される
    return 0;
}

補足: 退化の設計意図

配列の退化はC言語の歴史的なデザインです。初期のC言語では参照が存在しなかったため、配列全体をコピーする必要があり、これは効率が悪かったです。そのため、「配列をポインタに退化させる」ルールが導入されました。しかし、これにより長さ情報が失われる欠点があります。C++では、この問題を解決するために「配列参照」が提供されています。

タグ: C++ Array template

5月28日 21:30 投稿