凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

可変長引数テンプレートをタプルで受け取ってそれをまた展開する方法アレコレ

はじめに

v8 絡みのコードで可変長引数テンプレートを扱いたくて困ってつぶやいていたら、アキラさん(id:faith_and_brave)が教えてくれました:

ありがとうございました!色々試してみましたのでメモ。

可変長引数をタプルで受け取って格納、後で1個1個取り出して使う

1個1個使うときは boost::fusion::for_each を使います。

#include <iostream>
#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/for_each.hpp>

namespace fusion = boost::fusion;

template <class... Args>
class X
{
public:
	X(Args&&... args)
		: args_(std::forward<Args>(args)...)
	{}

	template <class F>
	void use_each_arg_by(F&& f) const
	{
		fusion::for_each(args_, f);
	}

private:
	fusion::vector<Args...> args_;
};

template <class... Args>
inline X<Args...> make_x(Args&&... args)
{
	return X<Args...>(std::forward<Args>(args)...);
}

struct print
{
	template <class T>
	void operator()(T&& val) const
	{
		std::cout << val << std::endl;
	}
};

int main()
{
	const auto x1 = make_x("hoge", 4);
	x1.use_each_arg_by(print());

	const auto x2 = make_x(1.5, 4.2f, 'a');
	x2.use_each_arg_by(print());
}
結果
hoge
4
1.5
4.2
a

可変長引数をタプルで受け取って格納、後でまとめて使う

boost::fusion::fused は関数の引数をパックして boost::fusion::vector で受け取れるものに変換してくれる感じです。そのヘルパ関数である boost::fusion::make_fused を使って以下の様な感じになります。

#include <iostream>
#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/make_fused.hpp>

namespace fusion = boost::fusion;

template <class... Args>
class X
{
public:
	X(Args&&... args)
		: args_(std::forward<Args>(args)...)
	{}

	template <class F>
	void use_all_args_by(F&& f) const
	{
		fusion::make_fused(f)(args_);
	}

private:
	fusion::vector<Args...> args_;
};

template <class... Args>
inline X<Args...> make_x(Args&&... args)
{
	return X<Args...>(std::forward<Args>(args)...);
}

int main()
{
	const auto x1 = make_x("hoge", 4);
	x1.use_all_args_by([](const std::string& s, int i) {
		std::cout << s << ' ' << i << std::endl;
	});
	const auto x2 = make_x(1.5, 4.2f, 'a');
	x2.use_all_args_by([](double d, float f, char c) {
		std::cout << d << ' ' << f << ' ' << c << std::endl;
	});
}
結果
hoge 4
1.5 4.2 a

可変長引数をタプルで受け取って格納、後で可変長引数に戻して処理

可変長引数の型 Args を保持しているクラス内ならそのまま Args を利用して処理出来ます。再帰で展開して先ほどと同じ処理をしてみます。先程は lambda 内で受け取る引数の型をわざわざ書きましたが、こうすれば後で型を覚えていなくても OK です。

#include <iostream>
#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/make_fused.hpp>

namespace fusion = boost::fusion;

template <class... Args>
class X
{
public:
	X(Args&&... args)
		: args_(std::forward<Args>(args)...)
	{}

	template <class F>
	void use_all_args_by(F&& f) const
	{
		fusion::make_fused(f)(args_);
	}

	void print() const
	{
		use_all_args_by([&](const Args&... args){
			print_impl(args...);
		});
	}

private:
	fusion::vector<Args...> args_;

	template <class Arg, class... RestArgs>
	void print_impl(const Arg& arg, const RestArgs&... rest_args) const
	{
		std::cout << arg << ' ';
		print_impl(rest_args...);
	}

	void print_impl() const
	{
		std::cout << std::endl;
	}
};

template <class... Args>
inline X<Args...> make_x(Args&&... args)
{
	return X<Args...>(std::forward<Args>(args)...);
}

int main()
{
	const auto x1 = make_x("hoge", 4);
	x1.print();
	const auto x2 = make_x(1.5, 4.2f, 'a');
	x2.print();
}
結果
hoge 4 
1.5 4.2 a 

おわりに

もっとこうすると楽だよ!みたいな方法があったら教えてください m(_ _)m。