🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

8996閲覧

ラムダ式の再帰でautoを使った場合の簡潔な書き方はあるのか?

raccy

総合スコア21737

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2016/11/03 12:10

編集2016/11/03 12:11

この質問はC++14での話です。C++17からはこうなるみたいな情報もあれば嬉しいです。

###概要

C++14からラムダ式の仮引数にもautoが使えるようになりましたが、このautoを使ったときにラムダ式の再帰を簡潔に書く方法がわかりません。

###サンプルコード
下記コードはコラッツの問題において、何ステップで1になるかを数える処理です(負の値の場合とか細かいエラー処理とかは省いています)。

C++

1#include <functional> 2#include <iostream> 3 4// C++11 での書き方 5int collatz_count1(int x) 6{ 7 std::function<int(int)> f = [&f](int n) { 8 if (n == 1) { 9 return 0; 10 } else if (n % 2 == 0) { 11 return f(n / 2) + 1; 12 } else { 13 return f(3 * n + 1) + 1; 14 } 15 }; 16 return f(x); 17} 18 19// C++14 でのみ動作 20int collatz_count2(int x) 21{ 22 auto f = [](auto g, auto n) { 23 if (n == 1) { 24 return 0; 25 } else if (n % 2 == 0) { 26 return g(g, n / 2) + 1; 27 } else { 28 return g(g, 3 * n + 1) + 1; 29 } 30 }; 31 return f(f, x); 32} 33 34// コンパイルエラー 35int collatz_count3(int x) 36{ 37 auto f = [&f](auto n) { 38 if (n == 1) { 39 return 0; 40 } else if (n % 2 == 0) { 41 return f(n / 2) + 1; 42 } else { 43 return f(3 * n + 1) + 1; 44 } 45 }; 46 return f(x); 47} 48 49// コンパイルエラー 50int collatz_count4(int x) 51{ 52 auto f = [&f](int n) { 53 if (n == 1) { 54 return 0; 55 } else if (n % 2 == 0) { 56 return f(n / 2) + 1; 57 } else { 58 return f(3 * n + 1) + 1; 59 } 60 }; 61 return f(x); 62} 63 64int main() 65{ 66 int i = 27; 67 std::cout << "collatz_count1: " << collatz_count1(i) << std::endl; 68 std::cout << "collatz_count2: " << collatz_count2(i) << std::endl; 69 std::cout << "collatz_count3: " << collatz_count3(i) << std::endl; 70 std::cout << "collatz_count4: " << collatz_count4(i) << std::endl; 71 return 0; 72}

collatz_count1collatz_count2はうまく動きます(collatz_count1はC++11でも)が、collatz_count3 collatz_count4はコンパイルエラーになります。fの型をdecltype(auto)にコンパイルエラーになります。

###何が問題なのか?
もっと汎用的にするため、ラムダ式の仮引数を(auto n)のようにした場合、collatz_count1のような書き方はできなくなると思っています。なぜならstd::function<int(int)>みたいな型をどのようにも書けないからです(かといってstd::function<int(int)>と書いてしまうと、せっかくのautointに確定してしまいます)。となると上のcollatz_count3 のような書き方で動いて欲しいのですが、fの型が確定していないためキャプチャできないとエラーになります。これはcollatz_count4も同じで、同じエラーとなります。そこで、collatz_count2のように、キャプチャではなく引数で渡せばうまく動きますが、簡潔とは言い難いです。

つまり、何がしたいかというと、

  • ラムダ式の仮引数の型はautoにしてテンプレートのように使いたい。
  • しかし、その場合の再帰はキャプチャでは実現できなくなるのをなんとかしたい。

なんとか方法はないのでしょうか?それとも現在のC++14の仕様ではcollatz_count2のように書くしかないのでしょうか?

###検証環境
OS: macOS 10.12.1
GCC: Homebrew gcc 6.2.0
Clang: Apple LLVM version 8.0.0 (clang-800.0.42.1)

-std=c++14付きでコンパイルしています。

###参考文献
C++ : ラムダ式のみでの再帰文 - Qiita
C++プログラマ キャスブログ
ラムダ式で再帰関数 - C++と色々
c++ - Recursive lambda functions in C++11 - Stack Overflow

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ラムダ式の自己再帰呼出しには、Recursive lambda functions in C++14 回答のように不動点コンビネータを介する方法があります。

→ 追記:書いた後に気づいたのですが本質的にはcollatz_count2と等価ですね。

C++

1template<typename Functor> 2struct fix_type { 3 Functor functor; 4 5 template<typename... Args> 6 decltype(auto) operator()(Args&&... args) const& 7 { return functor(functor, std::forward<Args>(args)...); } 8}; 9 10template<typename Functor> 11fix_type<typename std::decay<Functor>::type> fix(Functor&& functor) 12{ return { std::forward<Functor>(functor) }; } 13 14int collatz_count5(int x) 15{ 16 auto f = fix([](auto&& f, int n) { 17 if (n == 1) { 18 return 0; 19 } else if (n % 2 == 0) { 20 return f(f, n / 2) + 1; 21 } else { 22 return f(f, 3 * n + 1) + 1; 23 } 24 }); 25 return f(x); 26}

(個人的にはラムダ式の"名前"が欲しいなら普通の関数でいいじゃない派なので、ネタとロマンの追求以外ではこのようなコーディングには賛同しませんが)

C++17からはこうなるみたいな情報もあれば嬉しいです。

C++1z(17)でのラムダ式まわりの言語拡張は *thisコピーキャプチャ と constexprラムダ ですから、本件に関しては実装の選択肢が増えたりしないと思います。私見ですが、autoによる静的型推論とC++の静的型システムの上で実現する以上、collatz_count2/collatz_count5以外の実装手段は将来的にもでてこないのではと思います。

投稿2016/11/05 12:05

編集2016/11/05 13:09
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

こんにちは。

近しい話題がnamed lambdaが必要な理由にありました。

autoは使えない。なぜなら、autoの名前は、その初期化式の中では、使えないからだ。

だそうです。
そして、named lamdaを使えばできるとか。N2511で提案されてます。規格に入る見込みは分かりませんが。

投稿2016/11/03 13:28

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2016/11/03 13:45

named lambdaっすか。なんかJavaScriptを思い出してしましたが、これ欲しいですね。
Chironian

2016/11/03 14:02

そうですね。最早「無名関数」と呼べなくなるのが残念(?)かも。 うはっ、今、N2511でぐぐったら、この質問が最初のページに出てきましたよ。 グーグルなんて仕事が速いんだ!!
raccy

2016/12/18 08:51

named lambdaが規格に入ることを期待して…ベストアンサーに! (ベストアンサーにしても、規格に入るかは無関係なんですけどねー)
Chironian

2016/12/18 09:51

あは。ありがとう。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問