- cpp17[meta cpp]
畳み込み式 (fold expression) は可変引数テンプレートのパラメータパックに対して二項演算を累積的に行う (畳み込む fold)。
( pack op ... ) // (1) 単項右畳み込み
( ... op pack ) // (2) 単項左畳み込み
( pack op ... op init ) // (3) 二項右畳み込み
( init op ... op pack ) // (4) 二項左畳み込み
-
op[italic]
-
pack[italic]
-
init[italic]
-
畳み込み式は括弧
()
で囲まれなければならない -
op
は後述する二項演算子
二項畳み込み (3, 4) の場合op
は同一でなければならない -
pack
は未展開のパラメータパック (規格ではキャスト式 cast-expression と呼ばれる) -
init
は未展開のパラメータパック以外 (規格ではキャスト式 cast-expression と呼ばれる) -
畳み込み式は以下のように展開される:
- 単項右畳み込みは
arg1 op (... op (argN-1 op argN))
- 単項左畳み込みは
((arg1 op arg2) op ...) op argN
- 二項右畳み込みは
arg1 op (... op (argN-1 op (argN op init)))
- 二項左畳み込みは
(((init op arg1) op arg2) op ...) op argN
ただし
argi
はパラメータパックの i 番目の要素 - 単項右畳み込みは
-
機能テストマクロは
__cpp_fold_expressions
オーバーロードを含めて以下の演算子を畳み込み式で使用できる:
+
-
*
/
%
^
&
|
=
<
>
<<
>>
+=
-=
*=
/=
%=
^=
&=
|=
<<=
>>=
==
!=
<=
>=
&&
||
,
.*
->*
単項畳み込み (1, 2) でパラメータパックが空の場合、以下の演算子については式の値が設定される:
演算子 | 値 |
---|---|
&& |
true |
|| |
false |
, |
void() |
上記以外の演算子に対し空のパラメータパックが適用された場合、プログラムは不適格となる。
空のパラメータパックが適用された場合の挙動を変えるには二項畳み込み (3, 4) で値を与える。
#include <iostream>
#include <string>
// 単項右畳み込みで和を計算
template<typename... Args> auto sum(Args... args)
{
return (args + ...);
}
// 二項右畳み込みで和を計算、フォールバック値 0 (算術型に対しては sum と同じ値を返すだろう)
template<typename... Args> auto sum0(Args... args)
{
return (args + ... + 0);
}
// 単項左畳み込みで引数が全て true かどうかを返す
template<typename... Args> bool all(Args... args)
{
return (... && args);
}
// 二項左畳み込みで引数を出力
template<typename... Args> void print_all(std::ostream& os, Args... args)
{
(os << ... << args);
}
int main()
{
using namespace std::string_literals;
std::cout << std::boolalpha;
std::cout << sum(1, 2, 3, 4, 5) << '\n'; // int の和
std::cout << sum("a"s, "b"s, "c"s) << '\n'; // std::string の連結
//std::cout << sum() << '\n'; // 不適格: 引数が必要
//std::cout << sum0("a"s, "b"s, "c"s) << '\n'; // 不適格: std::string + int
std::cout << sum0() << '\n'; // 引数がないので設定した 0 にフォールバック
std::cout << all() << '\n'; // 引数がないので true にフォールバック
print_all(std::cout, 1, 2, 3, '\n');
}
15
abc
0
true
123
今までは、累積的に二項演算を行うには以下のように可変長引数関数を再帰的に呼び出さなければならなかった:
auto sum() { return 0; }
template<typename T> auto sum(T t) { return t; }
template<typename T, typename... Ts> auto sum(T t, Ts... ts) { return t + sum(ts...); }
畳み込み式によってこれを簡潔に書けるようになった。
N4191, N4295 では以下の演算子についてもパラメータパックが空のときに値を設定することが提案されていた:
演算子 | 値 |
---|---|
* |
1 |
+ |
int() |
& |
-1 |
| |
int() |
二項演算の単位元を設定するのは自然なことだと考えられた。
しかしながら、operator+
をオーバーロードしてコンテナや文字列の連結に用いる (cf. std::string
) のは一般的なことである。
そのような場合、空のパラメータパックを与えたときに int()
が適用されるのは思わぬ挙動につながり、
しかもバグ発見が困難であることが
N4358
で指摘された。
N4072 Fixed Size Parameter Packs
との兼ね合いもあったが、
上記のフォールバック値は削除され、パラメータパックが空の場合は不適格となった。
一方 operator&&
, operator||
, operator,
はオーバーロードがバッドプラクティスとされているため残された。
空のパラメータパックが与えられた場合、単項畳み込み式は以下のように実装される empty_fold
オブジェクトを返す:
template<typename BinaryFunction> struct empty_fold
{
template<typename T> constexpr operator T() const
{
return identity_element<T, BinaryFunction>;
}
};
identity_element
は単位元を持つあらゆるマグマに対して特殊化され、例えば std::string::operator+
では ""s
とする。
演算の左単位元と右単位元が異なる場合は、それぞれ left_identity_element
と right_identity_element
を定義する。
それらは特殊化されなかった場合 identity_element
にフォールバックする。
この方法は可能な限りジェネリックであるが、以下のような問題があった:
- 空のパラメータパックは型付けされていないが、返り値の型は他の型に文脈的に変換可能である (暗黙の型変換につながり得る)
- テンプレート特殊化だけのために関数をオブジェクトしなければならない
- サポートする演算子と等価な関数オブジェクト間のマッピングを用意しなければならない
結局のところこれは問題の解決にはつながらず、負担も大きいことから採用されなかった。
可能であれば空の畳み込みから返り値の型推論を行い、そうでなければプログラムを不適格とする。
例えば以下の例では std::string
に推論する:
auto res = (std::string(args) + ...);
これは N4072 Fixed Size Parameter Packs と適合するが、以下のような問題があった:
- 空の単項畳み込みの型が必ずしも正しく推論されない
- 単位元を持つ演算でしか機能しない
ルールが多い割に利点があまりないため採用されなかった。
~
も畳み込み式の演算子として
N4191,
N4295
で提案されていたが削除された (理由は発見できず)。
- C++11 可変引数テンプレート
std::accumulate
— イテレータの範囲について累積的に二項演算を行う