C++で引数を遅延評価してみる

# やりたいこと
C++で引数を遅延評価[^1]したい。
具体的にはScalaの名前渡し＋lazyみたいなことをしたい。

# templateでやってみた
受け取る関数をtemplateにしてlambdaで渡せばいい。こんな感じ↓

```cpp:main.cpp
#include <iostream>
#include "lazy.hpp"

int ret_1()
{
    std::cout << "calc" << std::endl;
    return 1;
}

template<typename T>
void test(T lazy)
{
    std::cout << "test start" << std::endl;
    std::cout << lazy() << std::endl;
}

int main()
{
    int a=2;
    test([&](){return ret_1()+a;});
}
```

```:出力
test start
calc
3
```

……これで出来るんだけど、templateにしちゃうと仮想関数にできない。
# template抜きでやってみた
ポリモーフィズムでlambdaの型を隠蔽してみた。
規格見る限り大丈夫だと思うんだけど、const&での多態が非合法だったら誰かオシエテ。

```cpp:main.cpp
#include <iostream>
#include "lazy.hpp"

int ret_1()
{
    std::cout << "calc" << std::endl;
    return 1;
}

void test(lazy<int> lazy)//lazy<評価結果の型>
{
    std::cout << "test start" << std::endl;
    std::cout << *lazy << std::endl;
}

int main()
{
    int a=2;
    test(lazy_arg(ret_1()+a));
}
```

```cpp:lazy.hpp
#pragma once
#include <type_traits>
#include <optional>

template<typename T>
class lazy_base
{
    static_assert(!std::is_reference<T>::value, "lazy_arg argument type must not be reference");
    public:
    virtual T& value()const noexcept=0;
    auto& operator*()const noexcept
    {
        return value();
    }
    virtual ~lazy_base()=default;
    private:
};

template<typename T>
using lazy=const lazy_base<T>&;

template<typename Return, typename Lambda>
class lazy_impl final : public lazy_base<Return>
{
    public:
    Return& value()const noexcept override
    {
        if(!impl)impl=std::move(arg());
        return *impl;
    }
    lazy_impl(Lambda &&arg)noexcept:arg{std::move(arg)}{}
    private:
    const Lambda arg;
    mutable std::optional<Return> impl;
};

template<typename T>
auto lazy_arg(T &&arg)
{
    return lazy_impl<decltype(arg()), T>{std::move(arg)};
}

#define lazy_arg(ARG) lazy_arg([&](){return ARG;})
```

```:出力
test start
calc
3
```
# std::asyncでやってみた
「std::async(std::launch::deferred,\[](){...})使えば良いのでは？」というツッコミを受けたのでやってみた。
しかし、「std::async＝並列処理」ってイメージだったよ……

```cpp:main.cpp
#include <future>
#include <iostream>

int ret_1()
{
    std::cout << "calc" << std::endl;
    return 1;
}

void test(std::future<int> future)
{
    std::cout << "test start" << std::endl;
    std::cout << future.get() << std::endl;
}

int main()
{
    int a=2;
    test(std::async(std::launch::deferred, [&](){return ret_1()+a;}));
}
```
```:出力
test start
calc
3
```

しっかり動く。標準でイケるしこれでいいかも。
ただ、asyncって重そうなイメージ[^2]なので時間測ってみる。
# 測ってみた
作ったやつ(lazy)、テンプレート＋ラムダ式、asyncの3つで速度比較してみた。
シンプルに10万回呼び出して所要時間を計測。
コードが長くなったので先に結果を記載。

```:結果
lazy  :   1930[us]
lambda:   1927[us]
async : 188728[us]
```

5回試して大体上記と同じ結果になることを確認。
3つの内、asyncが少々遅い。
とはいえ、10万回呼び出してようやく約200,000μsなのでそこまで気にしなくていいかな。

```cpp:計測コード
#include <chrono>
#include <future>
#include <iostream>
#include <ios>
#include <iomanip>
#include "lazy.hpp"

int ret_1()
{
    std::cout << "";
    return 1;
}

void test1(lazy<int> lazy)
{
    *lazy;
}

template<typename T>
void test2(T lazy)
{
    lazy();
}

void test3(std::future<int> future)
{
    future.get();
}

template<typename T>
std::chrono::microseconds measure(T func)
{
    using namespace std::chrono;
    auto start=system_clock::now();
    for(int i=0;i<100000;++i)
    {
        func(i);
    }
    auto end=system_clock::now();
    return duration_cast<microseconds>(end-start);
}

int main()
{
    auto diff1=measure([&](int i){test1(lazy_arg(ret_1()+i));});
    auto diff2=measure([&](int i){test2([&](){return ret_1()+i;});});
    auto diff3=measure([&](int i){test3(std::async(std::launch::deferred,[&](){return ret_1()+i;}));});
  
    std::cout<<"lazy  :"<<std::right<<std::setw(7)<<diff1.count()<<"[us]"<<std::endl;
    std::cout<<"lambda:"<<std::right<<std::setw(7)<<diff2.count()<<"[us]"<<std::endl;
    std::cout<<"async :"<<std::right<<std::setw(7)<<diff3.count()<<"[us]"<<std::endl;
}
```
```:オプションetc
g++ prog.cc -Wall -Wextra -O2 -march=native -I/opt/wandbox/boost-1.67.0/gcc-8.1.0/include -std=c++17
```

***
[^1]: この記事ではcall by needを指す
[^2]: 人それを偏見という