LoginSignup
9
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-23

やりたいこと

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


  1. この記事ではcall by needを指す 

  2. 人それを偏見という 

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7