キャプチャを含むラムダ式を関数ポインタ化


**みなさん、ラムダ式を関数ポインタに変換したいですよね？？**

ラムダ式を関数ポインタに変換できると、関数ポインタを受け取る関数に直接ラムダ式を渡せて便利です。

うれしいことに、キャプチャを含まないラムダ式は関数ポインタに暗黙変換できます。

```cpp
std::atexit([] { std::println("Goodbye!"); });
```

しかし、ラムダ式がキャプチャを含んでいると関数ポインタに変換することはできません。
かなしい！

```cpp
std::string name = "Jack";
std::atexit([=] { std::println("Goodbye, {}!", name); });
// error: cannot convert 'main()::<lambda()>' to 'void (*)()'
```

**これちょっと不便ですよね？　ね？？**

というわけで、この記事では、なんかいろいろどうにかしてキャプチャを含むラムダ式を関数ポインタに変換します。



# キャプチャせずに使える変数

ラムダ式で使うすべての変数にキャプチャが必要なわけではありません。
static 変数などはキャプチャせずともラムダ式から使えると cppreference に書いてます[^1]。

[^1]: 規格書からこれに関する記述は見つけられませんでした。どこに書いてあるんだ？

> A lambda expression can use a variable without capturing it if the variable
> - is a non-local variable or has static or thread local [storage duration](https://en.cppreference.com/w/cpp/language/storage_duration) (in which case the variable cannot be captured), or
> - is a reference that has been initialized with a [constant expression](https://en.cppreference.com/w/cpp/language/constant_expression#Constant_expression).
> 
> *Reference: https://en.cppreference.com/w/cpp/language/lambda*

これを利用して、キャプチャありのラムダ式を static 変数に代入し、それをキャプチャなしのラムダ式から呼び出します。
これによりキャプチャがなくなるので、関数ポインタに変換することができます。
やったね！

```cpp
#include <print>
#include <string>
#include <type_traits>
#include <utility>
#include <cstdlib>

// lambda to function の tofu の部分
template <class F>
std::add_pointer_t<void()> tofu(F&& f) {
	static auto fu = std::forward<F>(f);  // static に置く
	return [] static { fu(); };  // キャプチャなしで呼び出せる！
}

int main() {
	std::string name = "Jack";
	std::atexit(tofu([=] { std::println("Goodbye, {}!", name); }));
}
```

:::note warn
ただし、 static 変数への代入は最初の呼び出し時にしか行われないので、同じ型のラムダ式に対して繰り返し使うことはできません。

```cpp
int main() {
	for (int i = 0; i < 3; ++i) {
		tofu([=] { std::println("i: {}", i); })();
	}
	// i: 0
	// i: 0
	// i: 0
}
```
:::

:::note warn
関数ポインタに変換されたラムダ式が持つオブジェクトは、プログラム終了まで破棄されません。
:::



# すべての引数と戻り値に対応

先ほど示したコードは `void()` にしか対応していなかったので、テンプレートをぐねぐねしてすべて[^3]の引数と戻り値の組み合わせに対応します。

[^3]: 「すべて」は嘘です。 `...` に対応してないので。

変換先の関数の型は `&F::operator()` の型を使って求めます[^2]。

[^2]: ジェネリックラムダを返すようにすれば引数や戻り値を気にせず実装できるんですが、なんか楽しくなっちゃって……。

```cpp
#include <type_traits>
#include <utility>

template <class F, class R, class... Args>
R (*tofu_impl(F&& f, R (std::remove_cvref_t<F>::*)(Args...)) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<F>, F>))(Args...) noexcept(std::is_nothrow_invocable_v<F&, Args...>) {
	static auto fu = std::forward<F>(f);
	return [](Args... args) static noexcept(std::is_nothrow_invocable_v<F&, Args...>) {
		return fu(std::forward<Args>(args)...);
	};
}

template <class F, class R, class... Args>
R (*tofu_impl(F&& f, R (std::remove_cvref_t<F>::*)(Args...) const) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<F>, F>))(Args...) noexcept(std::is_nothrow_invocable_v<const F&, Args...>) {
	static const auto fu = std::forward<F>(f);
	return [](Args... args) static noexcept(std::is_nothrow_invocable_v<const F&, Args...>) {
		return fu(std::forward<Args>(args)...);
	};
}

template <class F>
auto tofu(F&& f) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<F>, F>) {
	return tofu_impl(std::forward<F>(f), &std::remove_cvref_t<F>::operator());
}
```

<!--
https://wandbox.org/permlink/ihK2RA9KwsY0lQU4
-->

`R (*tofu_impl(F&& f, R (std::remove_cvref_t<F>::*)(Args...)) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<F>, F>))(Args...) noexcept(std::is_nothrow_invoable_v<F&, Args...>)` とか読みにくすぎて草。
どれがどれの `noexcept` だよ。
みんなは読めるかな？（）



# 使用例

## 例1

```cpp
int main() {
	std::string section;
	std::set_terminate(tofu([&] {
		std::println("Error during {}", section);
		std::cout << std::flush;
		std::abort();
	}));

	section = "initialisation";
	std::string text = "--42";
	
	section = "parsing";
	int value = std::stoi(text);

	section = "output";
	std::println("stoi({:?}) = {}", text, value);
}
```

```
Error during parsing
```

## 例2

```cpp
int main() {
	const std::vector<int> a{ 3, 1, 4, 1, 5, 9, 2 };
	std::vector<std::size_t> indices(std::from_range, std::views::iota(0uz, a.size()));

	std::qsort(indices.data(), indices.size(), sizeof(indices[0]), tofu([&](const void* x, const void* y) {
		const auto lhs = a[*static_cast<const std::size_t*>(x)];
		const auto rhs = a[*static_cast<const std::size_t*>(y)];
		if (lhs < rhs) return -1;
		if (lhs > rhs) return 1;
		return 0;
	}));

	for (std::size_t i : indices) {
		std::print(" [{}] ", i);
	}
	std::println();
	std::println("{::3}", indices | std::views::transform([&](std::size_t i) { return a[i]; }));
}
```

```
 [1]  [3]  [6]  [0]  [2]  [4]  [5] 
[  1,   1,   2,   3,   4,   5,   9]
```

~~`std::ranges::sort()` 使えばよくね？~~

## 例3

```cpp
int main() {
	const std::vector<int> a{ 2, 7, 1, 8, 2, 8, 1, 8, 2, 8 };
	
	int sum1 = 0;
	int sum2 = 0;
	const std::size_t mid = a.size() / 2;
	
	pthread_t t;
	pthread_create(&t, nullptr, tofu([&](void*) -> void* {
		for (std::size_t i = 0; i < mid; ++i) {
			sum1 += a[i];
		}
		return nullptr;
	}), nullptr);

	for (std::size_t i = mid; i < a.size(); ++i) {
		sum2 += a[i];
	}

	pthread_join(t, nullptr);

	std::println("sum: {}", sum1 + sum2);
}
```

```
sum: 47
```

~~`std::thread` 使えばよくね？~~



# おわりに

いかがでしたか？
static 変数を使うことでキャプチャを含むラムダ式も関数ポインタに変換することができました。
これでコーディング効率爆アゲ間違いなし！
ぜひ活用してみてください！