C++
関数からいくつかの値を返す
サーチ…
前書き
関数からいくつかの値を返すと便利な場面が数多くあります。例えば、項目を入力して価格と数値を返す場合、この機能は役に立ちます。 C ++でこれを行うには多くの方法があり、ほとんどがSTLに関係します。しかし、何らかの理由でSTLを避けたいのであれば、 structs/classes
やarrays
を含め、これを行うにはまだいくつかの方法がありarrays
。
出力パラメータの使用
パラメータは、1つ以上の値を返すために使用できます。それらのパラメータは非const
ポインタまたは参照であることが要求される。
参考文献:
void calculate(int a, int b, int& c, int& d, int& e, int& f) {
c = a + b;
d = a - b;
e = a * b;
f = a / b;
}
ポインタ:
void calculate(int a, int b, int* c, int* d, int* e, int* f) {
*c = a + b;
*d = a - b;
*e = a * b;
*f = a / b;
}
一部のライブラリやフレームワークでは、空の 'OUT' #define
を使用して、どのパラメータが関数シグニチャの出力パラメータであるかを#define
に明示します。これには機能上の影響はなく、コンパイルされますが、関数のシグネチャは少し明確になります。
#define OUT
void calculate(int a, int b, OUT int& c) {
c = a + b;
}
std :: tupleを使う
std::tuple
型は、潜在的に異なる型の値を含む任意の数の値を単一の戻りオブジェクトにバンドルできます。
std::tuple<int, int, int, int> foo(int a, int b) { // or auto (C++14)
return std::make_tuple(a + b, a - b, a * b, a / b);
}
C ++ 17では、括弧付きの初期化リストを使用できます。
std::tuple<int, int, int, int> foo(int a, int b) {
return {a + b, a - b, a * b, a / b};
}
返されたtuple
から値を取得するのは面倒なので、 std::get
テンプレート関数を使う必要があります:
auto mrvs = foo(5, 12);
auto add = std::get<0>(mrvs);
auto sub = std::get<1>(mrvs);
auto mul = std::get<2>(mrvs);
auto div = std::get<3>(mrvs);
関数が返る前に型を宣言できれば、 tuple
を既存の変数に展開するためにstd::tie
を使うことができます:
int add, sub, mul, div;
std::tie(add, sub, mul, div) = foo(5, 12);
戻り値の1つが必要ない場合、 std::ignore
を使用できます。
std::tie(add, sub, std::ignore, div) = foo(5, 12);
ストラクチャードバインディングを使用してstd::tie
を回避できます。
auto [add, sub, mul, div] = foo(5,12);
あなたの代わりに値のタプルの左辺値参照のタプルを返すようにしたい場合は、使用std::tie
する代わりにstd::make_tuple
。
std::tuple<int&, int&> minmax( int& a, int& b ) {
if (b<a)
return std::tie(b,a);
else
return std::tie(a,b);
}
許可する
void increase_least(int& a, int& b) {
std::get<0>(minmax(a,b))++;
}
まれに、 std::tie
代わりにstd::tie
std::forward_as_tuple
使用します。一時的なものが消費されるのに十分な長さではないかもしれないので、あなたがそうするならば注意してください。
std :: arrayを使う
コンテナstd::array
は、固定数の戻り値を一緒に束ねることができます。この数値はコンパイル時に知る必要があり、すべての戻り値は同じ型でなければなりません:
std::array<int, 4> bar(int a, int b) {
return { a + b, a - b, a * b, a / b };
}
これは、 int bar[4]
という形式のcスタイルの配列を置き換えます。利点は、さまざまなc++
std関数が今ではそれで使用できることです。また、バインドされたチェック付きの安全なメンバアクセス関数と、計算なしで配列のサイズを返すことができるsize
などat
便利なメンバ関数も用意size
います。
std :: pairを使用する
構造体テンプレートstd::pair
は、2つの型の正確に 2つの戻り値を束ねることができます。
#include <utility>
std::pair<int, int> foo(int a, int b) {
return std::make_pair(a+b, a-b);
}
C ++ 11以降では、 std::make_pair
代わりにイニシャライザリストを使用できます。
#include <utility>
std::pair<int, int> foo(int a, int b) {
return {a+b, a-b};
}
ペアのfirst
およびsecond
メンバオブジェクトを使用して、返されたstd::pair
の個々の値を取り出すことができます。
std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;
出力:
10
構造体を使う
struct
を使用して複数の戻り値をstruct
することができます。
struct foo_return_type {
int add;
int sub;
int mul;
int div;
};
foo_return_type foo(int a, int b) {
return {a + b, a - b, a * b, a / b};
}
auto calc = foo(5, 12);
個々のフィールドに代入する代わりに、コンストラクタを使用して戻り値の構築を簡単にすることができます。
struct foo_return_type {
int add;
int sub;
int mul;
int div;
foo_return_type(int add, int sub, int mul, int div)
: add(add), sub(sub), mul(mul), div(div) {}
};
foo_return_type foo(int a, int b) {
return foo_return_type(a + b, a - b, a * b, a / b);
}
foo_return_type calc = foo(5, 12);
関数foo()
が返す個々の結果は、 struct
calc
メンバ変数にアクセスすることで取得できます。
std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';
出力:
17 -7 60 0
注意: struct
を使用する場合、戻り値は単一のオブジェクトにグループ化され、意味のある名前を使用してアクセス可能です。これは、返された値の範囲内に作成された無関係な変数の数を減らすのにも役立ちます。
関数から返されたstruct
をアンパックするために、 構造化された束縛を使用することができます。これは、out-parametersをin-parametersを使用して均等に配置します。
int a=5, b=12;
auto[add, sub, mul, div] = foo(a, b);
std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';
このコードの出力は上記と同じです。 struct
は引き続き関数の値を返すために使用されます。これにより、フィールドを個別に処理することができます。
構造化バインディング
C ++ 17ではストラクチャードバインディングが導入されています。これにより、 std::tie()
に依存する必要がないため、または手動タプルのアンパック処理を行う必要がないため、複数の戻り値の型を扱う方が簡単です。
std::map<std::string, int> m;
// insert an element into the map and check if insertion succeeded
auto [iterator, success] = m.insert({"Hello", 42});
if (success) {
// your code goes here
}
// iterate over all elements without having to use the cryptic 'first' and 'second' names
for (auto const& [key, value] : m) {
std::cout << "The value for " << key << " is " << value << '\n';
}
構造化バインディングは、 std::pair
、 std::tuple
、および非静的データメンバーがパブリック直接メンバーまたは明白な基本クラスのメンバーであるすべての型で、デフォルトで使用できます。
struct A { int x; };
struct B : A { int y; };
B foo();
// with structured bindings
const auto [x, y] = foo();
// equivalent code without structured bindings
const auto result = foo();
auto& x = result.x;
auto& y = result.y;
あなたのタイプを "タプルのようなもの"にすると、あなたのタイプも自動的に動作します。タプルのような型は、適切なtuple_size
、 tuple_element
を持つ型でありget
書かれる:
namespace my_ns {
struct my_type {
int x;
double d;
std::string s;
};
struct my_type_view {
my_type* ptr;
};
}
namespace std {
template<>
struct tuple_size<my_ns::my_type_view> : std::integral_constant<std::size_t, 3>
{};
template<> struct tuple_element<my_ns::my_type_view, 0>{ using type = int; };
template<> struct tuple_element<my_ns::my_type_view, 1>{ using type = double; };
template<> struct tuple_element<my_ns::my_type_view, 2>{ using type = std::string; };
}
namespace my_ns {
template<std::size_t I>
decltype(auto) get(my_type_view const& v) {
if constexpr (I == 0)
return v.ptr->x;
else if constexpr (I == 1)
return v.ptr->d;
else if constexpr (I == 2)
return v.ptr->s;
static_assert(I < 3, "Only 3 elements");
}
}
今これは動作します:
my_ns::my_type t{1, 3.14, "hello world"};
my_ns::my_type_view foo() {
return {&t};
}
int main() {
auto[x, d, s] = foo();
std::cout << x << ',' << d << ',' << s << '\n';
}
関数オブジェクトコンシューマの使用
複数の関連する値で呼び出される消費者を提供することができます。
template <class F>
void foo(int a, int b, F consumer) {
consumer(a + b, a - b, a * b, a / b);
}
// use is simple... ignoring some results is possible as well
foo(5, 12, [](int sum, int , int , int ){
std::cout << "sum is " << sum << '\n';
});
これは「継承通過スタイル」として知られています。
タプルを返す関数を、次のようにスタイルを継承する継承関数に適合させることができます。
template<class Tuple>
struct continuation {
Tuple t;
template<class F>
decltype(auto) operator->*(F&& f)&&{
return std::apply( std::forward<F>(f), std::move(t) );
}
};
std::tuple<int,int,int,int> foo(int a, int b);
continuation(foo(5,12))->*[](int sum, auto&&...) {
std::cout << "sum is " << sum << '\n';
};
より複雑なバージョンはC ++ 14またはC ++ 11で書き込み可能です。
std :: vectorを使う
std::vector
は、同じ型の動的変数を返すのに便利です。次の例では、 int
をデータ型として使用していますが、 std::vector
は簡単にコピー可能な任意の型を保持できます。
#include <vector>
#include <iostream>
// the following function returns all integers between and including 'a' and 'b' in a vector
// (the function can return up to std::vector::max_size elements with the vector, given that
// the system's main memory can hold that many items)
std::vector<int> fillVectorFrom(int a, int b) {
std::vector<int> temp;
for (int i = a; i <= b; i++) {
temp.push_back(i);
}
return temp;
}
int main() {
// assigns the filled vector created inside the function to the new vector 'v'
std::vector<int> v = fillVectorFrom(1, 10);
// prints "1 2 3 4 5 6 7 8 9 10 "
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
return 0;
}
出力イテレータの使用
出力イテレータを関数に渡すことで、同じ型のいくつかの値を返すことができます。これは、一般的な関数(標準ライブラリのアルゴリズムなど)で特に一般的です。
例:
template<typename Incrementable, typename OutputIterator>
void generate_sequence(Incrementable from, Incrementable to, OutputIterator output) {
for (Incrementable k = from; k != to; ++k)
*output++ = k;
}
使用例:
std::vector<int> digits;
generate_sequence(0, 10, std::back_inserter(digits));
// digits now contains {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}