やりたいこと
C++で引数を遅延評価1したい。
具体的にはScalaの名前渡し+lazyみたいなことをしたい。
templateでやってみた
受け取る関数をtemplateにしてlambdaで渡せばいい。こんな感じ↓
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&での多態が非合法だったら誰かオシエテ。
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));
}
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=並列処理」ってイメージだったよ……
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なのでそこまで気にしなくていいかな。
計測コード
#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