きっかけ
以前、Pythonでカイ二乗検定を行ったときに、下記コードを使用していた。今改めてコードを見た時に、Pythonには多重代入(複数の変数に値を代入する)があったことを思い出した。多重代入について調べてみると、Python以外にも、Ruby、Scala、Perlなど多くの言語で、同じもしくは似た機能があるようです。
from scipy import stats
# カイ二乗検定
# 多重代入をしている
squared,p,dof,ef = stats.chi2_contingency(dat)
[C++17]構造化束縛
C++でも多重代入出来れば良いのにと思い調べてみると、C++17で構造化束縛という機能が実装されていました。早速どんなものなのか試してみました。
定義
「構造化束縛 (structured bindings)」は、組やタプル、配列や構造体を分解して各要素を取り出す機能である。この機能は、他の言語では「多重代入 (mutiple assignment, Python言語やRuby言語)」や「分割代入 (Destructuring assignment, JavaScript言語)」といった名称で知られている。
(引用先) cpprefjp - C++日本語リファレンス:構造化束縛
使用環境
- Visual Studio Community 2017
- コンパイル時有効になっているバージョン /std:c++latest
※Visual Studio 2017のC++の言語標準はデフォルトでC++14(/std:c++14)になっているため、C++17(/std:c++latest)に設定しないとエラーになります。
使用コード
今回は、私がC++でよく使うstd::pair、std::map、std::unordered_mapを用いて、試してみました。他にも、cpprefjp - C++日本語リファレンス:構造化束縛によると、配列やstd::tupleやクラスのPublicメンバ変数にも使用可能とあるため、多くの場面で使えそうな機能である。(クラスのprivateメンバ変数、静的メンバ変数、定数は対応していない)
int main(){
//std::pair
std::pair<int,std::string> testPair{0,"test" };
auto[key, value] = testPair;
std::cout << key << "," << value << std::endl;
//出力結果
//0,test
//std::map
std::map<int, std::string> mapHash = {
{ 0,"red" },
{ 1,"green" },
{ 2,"blue" },
};
//いままではこの書き方
for (const auto& d : mapHash)
{
std::cout << d.first << "," << d.second << std::endl;
}
//C++17ではfor文内でも使用出来る!!
for (const auto& [key2, value2] : mapHash){
std::cout << key2 << "," << value2 << std::endl;
}
//出力結果
//o,red
//1,green
//2,blue
//std::unordered_map
std::unordered_map<int, std::string> unordered_mapHash = {
{ 0,"red" },
{ 1,"green" },
{ 2,"blue" },
};
for (const auto& [key3, value3] : unordered_mapHash){
std::cout << key3 << "," << value3 << std::endl;
}
//出力結果
//o,red
//1,green
//2,blue
}
ただし、当たり前かと思われるかもしれないが、使用しない変数を指定した場合、エラーが出るため、注意が必要です。
int main(){
std::pair<int,std::string> testPair{0,"test" };
//使用しない変数があるため、エラーが発生する
auto[key, value,bug] = testPair;
}
他にも、自作クラス内で定義した変数を全取得したいときに、下記みたいに実装することも可能である。いままでの私のコードでは、自分でわかりやすくするために複数のローカル変数を使用していたが、構造化束縛により、変数宣言とデータ構造の分解が同時出来ることから、(自分の中では特に)このようなコードが減ると思われる。
//自作クラス
class TestAccessor
{
public:
TestAccessor() {}
void AddData(const int key, const std::string &value)
{
_data[key] = value;
}
public:
using iterator = std::map<int, std::string>::iterator;
using constIterator = std::map<int, std::string>::const_iterator;
//std::mapのイテレータを使用する
iterator begin() {
return _data.begin();
}
iterator end() {
return _data.end();
}
constIterator begin() const{
return _data.begin();
constIterator end() const {
return _data.end();
}
private:
std::map<int, std::string> _data;
};
int main()
{
TestAccessor accessor;
accessor.AddData(0, "red");
accessor.AddData(1, "green");
accessor.AddData(2, "blue");
//分かりやすくするために、ローカル変数に代入していた
for (const auto& data : accessor)
{
int key = data.first;
std::string value = data.second;
std::cout << key << "," << value << std::endl;
}
//ローカル変数を使用しなくていい!!
//わかりやすくなる
for (const auto& [key, value] : accessor)
{
std::cout << key << "," << value << std::endl;
}
//出力結果
//o,red
//1,green
//2,blue
}
結果
いままで、C++のレガシーな書き方を多く使用してきた。しかし、C++の新しいバージョンで増えた機能、書き方を使用することで、可読性が上がり、自作故のバグが減ると思います。これから新しいバージョンで追加された機能も追いかけていきます。
私はまだまだC++の機能等について勉強不足なので、何か間違いなどあればコメントで情報提供よろしくお願いします。