その他の構文

ここではひとつのページを作るほどでもない、ちょっとした構文について説明します。

変数等の初期化構文はオブジェクトの初期化の項を参照してください。

関数の後置戻り値型

関数の戻り値の型は通常は関数名の前に記述しますが、戻り値型にautoを指定し、引数リストの後方で->に続いて戻り値の型を記述することができます。


//int型
auto f1() -> int    
{
	return 1 + 2;
}

//short型
auto f2() -> short  
{
	return 1 + 2;
}

//int型(戻り値からの型推論)
auto f3() -> auto  
{
	return 1 + 2;
}

後置戻り値型の関数は引数リストの後ろに書かれるため、引数を参照することができます。
これは関数テンプレートの実引数からdecltypeを使用してデータ型を取得する場合に役立ちます。


//double型
auto f1(int a, double b) -> decltype(a + b)
{
	return a + b;
}

//a + bの式から型推論
template<typename T, typename U>
auto f2(T a, U b) -> decltype(a + b)
{
	return a + b;
}

int main()
{
	//double型
	auto a = f1(1, 2.0);

	//int型
	auto b = f2('a', 1);
}

他の関数の戻り値の型をそのまま戻り値の型にすることもできます。


#include <string>

//絶対値を得る関数
int _myAbs(int a) {
	return a < 0 ? -a : a;
}
double _myAbs(double a) {
	return a < 0 ? -a : a;
}
std::string _myAbs(const std::string& a) {
	//「.」が含まれていたら小数型とみなす
	if (a.find(".") == std::string::npos) {
		return std::to_string(_myAbs(std::stoi(a)));
	}
	return std::to_string(_myAbs(std::stod(a)));
}

//戻り値の型を_myAbs関数の戻り値の型と同じにする
template<typename T>
auto abs(T a) -> decltype(_myAbs(a))
{
	return _myAbs(a);
}

int main()
{
	auto a = abs(-123);		//int型
	auto b = abs(-1.23);	//double型
	auto c = abs("-1.23");	//std::string型
}

戻り値の型の後置は、全ての関数修飾および例外仕様の後ろに指定します。


class C
{
    auto f() const & noexcept -> void {}
};

範囲for文

C++11からfor文の仕様が拡張され、範囲for文という新しい記法がサポートされています。
これは他のプログラミング言語ではforeachというキーワードで用意されているもので、機能的にもほぼ同等です。
範囲for文は、配列やコンテナクラスなどの同じデータ型の集合に対して、全ての要素に一度ずつアクセスし処理を行うことができます。


std::vector<int> vec{ 0, 1, 2, 3, 4 };

//範囲for文
for (int v : vec)
{
	std::cout << v << std::endl;
}
0
1
2
3
4

このコードのfor文は、vecの先頭から要素をint型として取り出し、vに格納し、ブロック内で何らかの処理を行う、という動作になります。
これをvecの全ての要素に対して繰り返します。
必要ならばcontinue文やbreak文を使用することもできます。

従来のfor文ではループの終了条件式や反復式などの指定があり、この指定次第で複雑なループ処理が可能です。
しかし「配列の全ての要素に一度ずつアクセスする」という条件で足りる場合は、範囲for文の方が簡潔に書け、かつ確実なループ処理が可能です。

autoとの併用

データ型の指定にはautoが使用可能です。
以下はvecの型から自動的にint型と型推論されます。


std::vector<int> vec{ 0, 1, 2, 3, 4 };

for (auto v : vec)
{
	std::cout << v << std::endl;
}

参照との併用

上記の範囲for文では元の配列やコンテナクラスの要素のコピーが変数に格納されます。
そのため、この変数を書き替えても元のデータを書き換えることはできません。


std::vector<int> vec{ 0, 1, 2, 3, 4 };

for (auto v : vec)
{
	v *= 2;
}

for (auto v : vec)
{
	std::cout << v << std::endl;
}
0
1
2
3
4

元のデータを書き換えたい場合は参照にすれば可能です。


std::vector<int> vec{ 0, 1, 2, 3, 4 };

//&を付けて参照にする
for (auto &v : vec)
{
	v *= 2;
}

for (auto v : vec)
{
	std::cout << v << std::endl;
}
0
2
4
6
8

constとの併用

要素を参照で受け取るとコピーのコストが掛かりませんが、要素が書き換えられてしまうおそれもあります。
元データを書き換えたくない場合はconst参照にすると良いでしょう。


std::vector<int> vec{ 0, 1, 2, 3, 4 };

for (const auto &v : vec)
{
	//コンパイルエラー
	v *= 2;
}

if文、switch文での変数初期式

for文では、そのfor文内でのみ有効な変数を初期化式で宣言することができます。
C++17からは、これと同じことをif文やswitich文でも可能です。


#include <iostream>

int func()
{
    return 0;
}

int main()
{
	if (int n = func(); n != 0) {
        std::cout << n << std::endl;
	}
    //変数nはこの時点で使用不可
}

例えばある関数の戻り値が特定のif文でのみ必要な場合は、簡潔な記法で戻り値を受け取る変数のスコープを狭くすることができます。


#include <iostream>
#include <memory>

int main()
{
	std::shared_ptr<int> sPtr = std::make_shared<int>(10);
	std::weak_ptr<int> wPtr(sPtr);

	if (auto tmp = wPtr.lock()) {
		std::cout << *tmp << std::endl;
	}
	else {
		std::cout << "参照切れ" << std::endl;
	}//変数tmpの寿命はここまで

	//従来の方法
	{
		auto tmp = wPtr.lock();
		if (tmp) {
			std::cout << *tmp << std::endl;
		}
		else {
			std::cout << "参照切れ" << std::endl;
		}
	}
}

構造化束縛

C++17からは、ペア型とタプル型構造化束縛という記法で取り出すことができます。


std::pair<std::string, int> pair = { "abc", 123 };

//std::pairのそれぞれの要素から型推論して
//変数keyとvalueを宣言して代入
auto [key, value] = pair;
std::cout << key << " : " << value << std::endl;
//abc : 123

//mapの各要素はstd::pair型
std::map<std::string, int> map = {
	{ "cat", 1 },
	{ "dog", 2 },
	{ "rabbit", 3 },
	{ "goat", 1 }
};

//範囲for文でも使用できる
//constや参照(&)も可能
for (const auto& [key, value] : map) {
	std::cout << key << " : " << value << std::endl;
}
//cat : 1
//dog : 2
//goat : 1
//rabbit : 3

右辺のオブジェクトが持つ要素が先頭から順番に左辺で宣言した変数に格納されます。
右辺オブジェクトの要素数と左辺の変数の数は一致している必要があります。

構造化束縛は配列、クラス、構造体も分解することができます。
クラス、構造体は、すべての非静的メンバ変数をその宣言した順番で受け取ることができます。
非静的なprivateメンバ変数がある場合は構造化束縛は使用できません。


struct S
{
    int x;
    int y;

    //静的メンバ変数やメンバ関数はあってもかまわない
    static int s;
    void f() {}

private:
    //int p;        //非静的なprivateメンバ変数はNG
    static int ps;  //OK
};

int main()
{
    int arr[] = { 1, 2, 3 };
    auto [arr1, arr2, arr3] = arr;

    S s = { 1, 2 };
    auto [s1, s2] = s;
}

なお、構造化束縛は変数の宣言のための記法です。
すでに宣言済みの変数に値を代入することはできません。