C++ 11では、既知のタイプでサイズが不明なstd :: arrayを受け取る関数(またはメソッド)をどのように書きますか?
// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6> arr2;
std::array<int, 95> arr3;
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
検索中にテンプレートを使用する提案を見つけましたが、それらは面倒(ヘッダーのメソッド定義)で、私が達成しようとしているものに対しては過度に思えます。
単純なCスタイルの配列のように、これを機能させる簡単な方法はありますか?
単純なCスタイルの配列のように、これを機能させる簡単な方法はありますか?
いいえ。関数を関数templateにしない限り(または、std::vector
、質問へのコメントで示唆されているように):
template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
実際の例 です。
array
のサイズは型の一部であるため、望みどおりのことはできません。いくつかの選択肢があります。
イテレータのペアを使用することをお勧めします。
template <typename Iter>
void mulArray(Iter first, Iter last, const int multiplier) {
for(; first != last; ++first) {
*first *= multiplier;
}
}
または、配列の代わりにvector
を使用します。これにより、タイプの一部としてではなく、実行時にサイズを保存できます。
void mulArray(std::vector<int>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
以下で試してみたところ、うまくいきました。
#include <iostream>
#include <array>
using namespace std;
// made up example
void mulArray(auto &arr, const int multiplier)
{
for(auto& e : arr)
{
e *= multiplier;
}
}
void dispArray(auto &arr)
{
for(auto& e : arr)
{
std::cout << e << " ";
}
std::cout << endl;
}
int main()
{
// lets imagine these being full of numbers
std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7};
std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12};
std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1};
dispArray(arr1);
dispArray(arr2);
dispArray(arr3);
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
dispArray(arr1);
dispArray(arr2);
dispArray(arr3);
return 0;
}
出力
1 2 3 4 5 6 7
2 4 6 8 10 12
1 1 1 1 1 1 1 1 1 1
3 6 9 12 15 18 21
10 20 30 40 50 60
2 2 2 2 2 2 2 2 2
絶対に、C++ 11には、既知の型でサイズが不明なstd :: arrayをとる関数を記述する簡単な方法があります。
関数に配列サイズを渡すことができない場合は、代わりに、配列の開始位置のメモリアドレスと、配列の終了位置の2番目のアドレスを渡すことができます。後で、関数内で、これら2つのメモリアドレスを使用して配列のサイズを計算できます!
#include <iostream>
#include <array>
// The function that can take a std::array of any size!
void mulArray(int* piStart, int* piLast, int multiplier){
// Calculate the size of the array (how many values it holds)
unsigned int uiArraySize = piLast - piStart;
// print each value held in the array
for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++)
std::cout << *(piStart + uiCount) * multiplier << std::endl;
}
int main(){
// initialize an array that can can hold 5 values
std::array<int, 5> iValues;
iValues[0] = 5;
iValues[1] = 10;
iValues[2] = 1;
iValues[3] = 2;
iValues[4] = 4;
// Provide a pointer to both the beginning and end addresses of
// the array.
mulArray(iValues.begin(), iValues.end(), 2);
return 0;
}
コンソールでの出力: 10、20、2、4、8
[〜#〜] edit [〜#〜]
C++ 20には暫定的にstd::span
が含まれます
https://en.cppreference.com/w/cpp/container/span
元の回答
必要なものはgsl::span
のようなものです。これは、C++コアガイドラインで説明されているガイドラインサポートライブラリで利用できます。
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views
GSLのオープンソースヘッダーのみの実装については、こちらをご覧ください。
https://github.com/Microsoft/GSL
gsl::span
を使用すると、次のことができます。
// made up example
void mulArray(gsl::span<int>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6> arr2;
std::array<int, 95> arr3;
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
std::array
の問題は、サイズがその型の一部であるため、任意のサイズのstd::array
を受け取る関数を実装するためにテンプレートを使用する必要があることです。
一方、gsl::span
は、そのサイズを実行時情報として保存します。これにより、1つの非テンプレート関数を使用して、任意のサイズの配列を受け入れることができます。また、他の隣接するコンテナも受け入れます。
std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};
mulArray(vec, 6);
mulArray(carr, 7);
かなりクールだよね?
これは実行できますが、きれいに実行するにはいくつかの手順が必要です。最初に、連続した値の範囲を表す_template class
_を記述します。次に、template
の大きさを知っているarray
バージョンを、この連続した範囲をとるImpl
バージョンに転送します。
最後に、_contig_range
_バージョンを実装します。 for( int& x: range )
は_contig_range
_で機能することに注意してください。これはbegin()
とend()
を実装しており、ポインターは反復子であるためです。
_template<typename T>
struct contig_range {
T* _begin, _end;
contig_range( T* b, T* e ):_begin(b), _end(e) {}
T const* begin() const { return _begin; }
T const* end() const { return _end; }
T* begin() { return _begin; }
T* end() { return _end; }
contig_range( contig_range const& ) = default;
contig_range( contig_range && ) = default;
contig_range():_begin(nullptr), _end(nullptr) {}
// maybe block `operator=`? contig_range follows reference semantics
// and there really isn't a run time safe `operator=` for reference semantics on
// a range when the RHS is of unknown width...
// I guess I could make it follow pointer semantics and rebase? Dunno
// this being tricky, I am tempted to =delete operator=
template<typename T, std::size_t N>
contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, std::size_t N>
contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, typename A>
contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};
void mulArrayImpl( contig_range<int> arr, const int multiplier );
template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
mulArrayImpl( contig_range<int>(arr), multiplier );
}
_
(テストされていませんが、設計は機能するはずです)。
次に、_.cpp
_ファイルで:
_void mulArrayImpl(contig_range<int> rng, const int multiplier) {
for(auto& e : rng) {
e *= multiplier;
}
}
_
これには、配列の内容をループするコードが(コンパイル時に)配列の大きさを知らないという欠点があり、最適化のコストがかかる可能性があります。実装をヘッダーに含める必要がないという利点があります。
_contig_range
_を明示的に構築する場合は注意してください。set
を渡すと、set
データが連続していると見なされ(false)、未定義の動作が行われます場所。これが機能することが保証されている唯一の2つのstd
コンテナは、vector
とarray
(およびCスタイルの配列です!)です。ランダムアクセスであるにもかかわらずdeque
は連続的ではなく(危険なことに、小さなチャンクで連続的です!)、list
はさらに近接していません。隣接しています。
したがって、基本的にベースをカバーする_std::array
_、_std::vector
_、およびCスタイルの配列で実装した3つのコンストラクター。
_[]
_の実装も同様に簡単です。また、for()
と_[]
_の間でarray
に必要なもののほとんどですよね。