ラムダ式の戻り値と引数の型を調べたい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
あごみつ
記事: 17
登録日時: 5年前

ラムダ式の戻り値と引数の型を調べたい

#1

投稿記事 by あごみつ » 5年前

はじめまして
C++のラムダ式についての質問です

引数として渡されたラムダ式の戻り値と引数の型を調べることはできますか?
キャプチャしないラムダ式なら関数ポインタに暗黙の変換ができるらしいので

コード:

template <typename R, typename... Args>
auto func(R (*)(Args...)) {}

int main(void)
{
    func([](int x, int y) { return x + y; });

    return 0;
}
は動くと思ったのですがMSVCでもGCCでも template argument deduction に失敗します
何か良い方法はないでしょうか?

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#2

投稿記事 by sleep » 5年前

あごみつ さんが書きました: 引数として渡されたラムダ式の戻り値と引数の型を調べることはできますか?
結論から述べると、ラムダ式(関数オブジェクト)から直接 戻り値と仮引数の型を調べることはできません。


回りくどく、擬似的に調べることはできますが、その場合も関数ポインタの型を明示しないとできません。(これはSTLのfunctionと同様です)

コード:

#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <memory>
#include <type_traits>
#include <functional>
using namespace std;

template<typename... Args>
class Type
{
public:
	static inline auto get() -> std::vector<const std::type_info*>
	{
		std::vector<const std::type_info*> type_list;
		type_list.reserve(std::tuple_size<Tuple_Type>::value);
		get<std::tuple_size<Tuple_Type>::value - 1>(type_list);
		return move(type_list);
	}
private:
	template<std::size_t index>
	static inline void get(std::vector<const std::type_info*>& type_list)
	{
		using Element_Type = typename std::tuple_element<index, Tuple_Type>::type;
		type_list.emplace_back(&typeid(Element_Type));
		get<index - 1>(type_list);
	}
	template<>
	static inline void get<0>(std::vector<const std::type_info*>& type_list)
	{
		using Element_Type = typename std::tuple_element<0, Tuple_Type>::type;
		type_list.emplace_back(&typeid(Element_Type));
	}
	template<>
	static inline void get<-1>(std::vector<const std::type_info*>& type_list)
	{
		type_list.emplace_back(typeid(void).name());
	}
public:
	using Tuple_Type = std::tuple<Args...>;
};

template<typename R, typename ...Args>
auto func(R(*)(Args...)) -> R
{
	cout << "戻値の型:" << typeid(R).name() << "\n"
		 << "引数の型:";
	std::vector<const std::type_info*> v = Type<Args...>::get();
	for (auto type : Type<Args...>::get())
		cout << type->name() << " "; cout << "\n" << endl;
	return 0;
}

template<typename Function_Type>
auto func(Function_Type f) -> decltype(func(f))
{
	return func(f);
}

int main()
{
	func<int(int, int)>([](int x, int y) { return x + y; });
	func<double(float, float, float)>([](float x, float y, float z) -> double { return x / y / z; });
	return 0;
}
また、ラムダ式から関数ポインタへの暗黙の型変換は、templateの展開時に 暗黙に型を置き換えてくれる訳ではありません。
(暗黙に型変換できるかどうかを)コンパイラが判断するのは、 templateの展開後となります。
そのため、templateへラムダ式を渡すコードを記述しても関数ポインタ型として展開されることはありません。

コード:

#include <iostream>
using namespace std;

template<typename R, typename ...Args>
auto func(R(*)(Args...)) -> R
{
	cout << typeid(R).name() << endl;
	return 0;
}

using Function_Type = int(int, int);
auto hoge(Function_Type f) -> decltype(func(f))
{
	return func(f);
}

int main()
{
	//func([](int x, int y) { return x + y; });  //template側の仮引数の型が明示されていないため、型の不一致により暗黙の型変換どころかtemplateがインスタンス化できない。
	hoge([](int x, int y) { return x + y; });  //仮引数の型が明示されているため、暗黙の型変換ができる。
	return 0;
}
あと、もし他の言語と同じようなことをしようとされていましたら
ジェネリックプログラミングについて、他言語との仕様の違いについて調べてみてください。


それと、目的に寄りますが
ラムダ式指定時に実引数も同時に渡すのであれば、以下のような方法で動作させることはできます。

コード:

#include <iostream>
#include <functional>
#include <thread>
#include <future>
using namespace std;

template<typename Func, typename ...Args>
auto worker(Func&& func, Args&&... args) -> packaged_task<typename result_of<Func(Args...)>::type()>
{
	return packaged_task<typename result_of<Func(Args...)>::type()>(bind(forward<Func>(func), forward<Args>(args)...));
}

int func(int a, int b, int c)
{
	return a * b * c;
}

int main()
{
	auto task1 = worker(func, 1, 2, 3);
	auto f1 = task1.get_future();
	thread(move(task1)).detach();
	cout << f1.get() << endl;

	auto task2 = worker([](int a, int b, int c){ return a * b * c; }, 4, 5, 6);
	auto f2 = task2.get_future();
	thread(move(task2)).detach();
	cout << f2.get() << endl;

	cin.ignore();
}

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#3

投稿記事 by sleep » 5年前

最初のコードの訂正
・38行目 typeid(void).name() ではなく、&typeid(void)
・49行目 不要

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#4

投稿記事 by sleep » 5年前

もう1点
・仮引数の型を左から指定された順で表示する場合 26行目と27行目は逆

あごみつ
記事: 17
登録日時: 5年前

Re: ラムダ式の戻り値と引数の型を調べたい

#5

投稿記事 by あごみつ » 5年前

ご回答ありがとうございます。

自分も色々試してみて、無理そうだと思いつつダメ元で質問したところにご回答をいただき落胆していたのですが、先ほどふと見ていた記事に
decltype([]{})::operator() によりラムダ式の型を推論して関数ポインタに変換する旨のことが書かれていまして、それを見て思いついたのですが、

コード:

#include <tuple>
#include <typeinfo>

template <class F, typename R, typename... ArgTypes> R _getResult(R (F::*)(ArgTypes...));
template <class F, typename R, typename... ArgTypes> R _getResult(R (F::*)(ArgTypes...) const);
template <class F> using getResultType = decltype(_getResult(&F::operator()));

template <class F, typename R, typename... ArgTypes> std::tuple<ArgTypes...> _getArgs(R (F::*)(ArgTypes...));
template <class F, typename R, typename... ArgTypes> std::tuple<ArgTypes...> _getArgs(R (F::*)(ArgTypes...) const);
template <class F> using getArgTypeList = decltype(_getArgs(&F::operator()));

// type_at: 型リストの I 番目の型を取得する

template <size_t, class>
struct _type_at;
template <size_t I, typename Head, typename... Tail>
struct _type_at<I, std::tuple<Head, Tail...>> : _type_at<I - 1, std::tuple<Tail...>> {};
template <typename Head, typename... Tail>
struct _type_at<0, std::tuple<Head, Tail...>> { using type = Head; };

template <size_t I, class TypeList>
using type_at = typename _type_at<I, TypeList>::type;

// ラムダ式の戻り値の型の名前を返す
template <class F>
auto getResultTypeName(F f)
{
	return typeid(getResultType<F>).name();
}

// ラムダ式の I 番目の引数の型の名前を返す
template <size_t I, class F>
auto getArgTypeName(F f)
{
	return typeid(type_at<I, getArgTypeList<F>>).name();
}

int main(void)
{
	auto f = [](int x, float y) -> double { return x * y; };

	getResultTypeName(f); // double
	getArgTypeName<0>(f); // int
	getArgTypeName<1>(f); // float

	return 0;
}
のように、戻り値の型と引数の型リストを取得できました

後は型リストをコンパイル時整数シーケンスで展開すれば生の型の配列が手に入ります
ちなみにこれ、どうやらキャプチャするラムダ式もそのまま使えることに気付きました

getResultType や getArgTypeList の前にいくつか特殊化を挟めば、関数ポインタやメンバ関数ポインタにも汎用的に使えそうです

[参考]
http://d.hatena.ne.jp/osyo-manga/20121205/1354674180

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#6

投稿記事 by sleep » 5年前

なるほど、賢いですね。operatorは関数ですからね。
これなら暗黙の型変換を宛にせずとも、直接 関数として関数ポインタの template に引き渡せますから 取り出すのは簡単ですね。

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#7

投稿記事 by sleep » 5年前

 
存在すら忘れてしまっていた件だったが・・・、朝御飯を食べていて急に思いついた。

どうやら本当の意味で型を取り出せてしまうようだ。
(※std::conditional は、仮引数が空のときに void を返せるようにするために使用)

コード:

#include <iostream>
#include <string>
#include <tuple>
#include <typeinfo>

template <typename R, typename... Args> auto getResultType(R(*)(Args...)) -> R;
template <typename F, typename R, typename... Args> auto getResultType(R(F::*)(Args...)) -> R;
template <typename F, typename R, typename... Args> auto getResultType(R(F::*)(Args...) const) -> R;
template <typename F, typename R = decltype(getResultType(&F::operator()))> auto getResultType(F) -> R;
#define Result_Type(f) decltype(getResultType(f))

template <std::size_t I, typename R, typename... Args> auto getArgsType(R(*)(Args...)) -> typename std::tuple_element<I, typename std::conditional<std::is_same<std::tuple<Args...>, std::tuple<>>::value, std::tuple<void>, std::tuple<Args...>>::type>::type;
template <std::size_t I, typename F, typename R, typename... Args> auto getArgsType(R(F::*)(Args...)) -> typename std::tuple_element<I, typename std::conditional<std::is_same<std::tuple<Args...>, std::tuple<>>::value, std::tuple<void>, std::tuple<Args...>>::type>::type;
template <std::size_t I, typename F, typename R, typename... Args> auto getArgsType(R(F::*)(Args...) const) -> typename std::tuple_element<I, typename std::conditional<std::is_same<std::tuple<Args...>, std::tuple<>>::value, std::tuple<void>, std::tuple<Args...>>::type>::type;
template <std::size_t I, typename F, typename R = decltype(getArgsType<I>(&F::operator()))> auto getArgsType(F) -> R;
#define Args_Type(i,f) decltype(getArgsType<i>(f))

int main()
{
	auto f = [](int, double, std::string) { return "abc"; };

	//ラムダ式から取り出した仮引数の型で、変数を宣言する。
	Result_Type(f) r = "address";
	Args_Type(0,f) a = 2;
	Args_Type(1,f) b = 10.08;
	Args_Type(2,f) c = "hello";

	std::cout << "[](int, double, std::string) { return \"abc\"; }" << std::endl;
	std::cout.width(9); std::cout << r << ": "; std::cout << typeid(r).name() << std::endl;
	std::cout.width(9); std::cout << a << ": "; std::cout << typeid(a).name() << std::endl;
	std::cout.width(9); std::cout << b << ": "; std::cout << typeid(b).name() << std::endl;
	std::cout.width(9); std::cout << c << ": "; std::cout << typeid(c).name() << std::endl;
	std::cout << std::endl;


	//別のラムダ式から取り出した仮引数の型で、新しいラムダ式の仮引数の型を構成する。
	auto f2 = [](Args_Type(1, f) x, Args_Type(0, f) y) { return x / y; };

	std::cout << "[](Args_Type(1, f) x, Args_Type(0, f) y) { return x / y; }" << std::endl;
	std::cout.width(9); std::cout << f2(b, a) << ": "; std::cout << typeid(Result_Type(f2)).name() << std::endl;

	std::cin.ignore();
	return 0;
}

アバター
nullptr
記事: 239
登録日時: 8年前

Re: ラムダ式の戻り値と引数の型を調べたい

#8

投稿記事 by nullptr » 5年前

decltype([]{})は仕様で禁止されています。コンパイルエラーです。
示されたリンク先のように、
auto f = []{};
decltype(f);
と変数にする必要があります。ラムダ式はdecltype式では書けません。
 
 
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
?
Is the は :
order C++? ✜
     糸冬   
  ――――――――
  制作・著作 NHK
 
 

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#9

投稿記事 by sleep » 5年前

まぁまぁ、その指摘は許してあげてください。

冒頭に以下のように前置きがありましたし
あごみつ さんが書きました: 引数として渡されたラムダ式の戻り値と引数の型を調べることはできますか?
参考にされただけのようで、おっしゃってるように一度変数に納めてからか
関数の仮引数で受け取った後、ということが前提のお話ではないでしょうかね。

実際にこのスレッド上に提示されているコードでも、直接decltypeにつっこんでいるコードは見受けられませんから
理解されていると割り切ってあげて良さそうじゃないでしょうかね?

おっしゃってるようにコンパイルから指摘される事項でもありますから。

アバター
nullptr
記事: 239
登録日時: 8年前

Re: ラムダ式の戻り値と引数の型を調べたい

#10

投稿記事 by nullptr » 5年前

単に第三者がこのトピックを見た時に誤解の元となる表現をされていると思ったので補足を述べたまでです。
せっかく誰でも読めるのですから。
質問者さんへの指摘という意味はあまりないので、気にしないでください。
 
 
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
?
Is the は :
order C++? ✜
     糸冬   
  ――――――――
  制作・著作 NHK
 
 

あごみつ
記事: 17
登録日時: 5年前

Re: ラムダ式の戻り値と引数の型を調べたい

#11

投稿記事 by あごみつ » 5年前

>> nullptr さま
申し訳ありません
もちろん理解した上での簡単のための記述でしたが、誤解を招く結果になってしまいました
ただ、sleep さまのご指摘の通り、俺の提示したコードはコンパイルが通るはずです

>> sleep さま
ええと……コード例が悪かったようです
型を取得するのが目的で、その後のことは考えていなかったので、サンプルがお粗末になってしまいましたが、
type_at で取っている以上、”本当の意味で”型が取得できるのは自明だったと思うのですが
とにかく、俺のものよりはるかに出来の良いコード例に感謝します

[追記]
既に解決したトピックですので、本来こちらに書くべきではないのですが、
ご指摘があってから、decltype([]{}) が禁止されている理由について考えていて思いついたので少しだけ
冒頭で、キャプチャしないラムダ式は関数ポインタに暗黙の変換ができる、と書きましたが、実はどんなラムダ式でも関数ポインタに変換できることに気が付きました

つまり、ラムダ式に対する直接の decltype が禁止されているのは、ラムダ式が「複数の同様の定義が、異なる型を持つ」という性質を持つから(だと思う)なので、ラムダ式を static に参照すれば、関数ポインタの出来上がりです

コード:

template <class T, typename... Args, class F,
	class = std::enable_if_t<std::is_convertible<F, T>::value>>
T toFuncPtr(F &&f) // キャプチャしないラムダ式
{
	return f;
}

template <class T, typename... Args, class F,
	class = std::enable_if_t<!std::is_convertible<F, T>::value>>
T toFuncPtr(F f) // キャプチャするラムダ式
{
	static F l = std::forward<F>(f);

	return [](Args... args)
	{
		// 今回は戻り値の型がvoidの時を考慮していない
		return l(std::forward<Args>(args)...);
	};
}

template <class F, size_t... I>
auto toFuncPtr(F &&f, std::index_sequence<I...>)
{
	using func_ptr_t = getResultType<F> (*)(type_at<I, getArgTypeList<F>>...);
	return toFuncPtr<func_ptr_t, type_at<I, getArgTypeList<F>>...>(std::forward<F>(f));
}

template <class F>
auto toFuncPtr(F &&f)
{
	return toFuncPtr(std::forward<F>(f), std::make_index_sequence<std::tuple_size<getArgTypeList<F>>::value>{});
}
問題は関数オブジェクトなのですが、ラムダ式でラップしてから渡すことで何とかなるかもしれません
もしそれが出来れば、Type Erasureを用いることなく、型情報を保持したstd::functionを作れます

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#12

投稿記事 by sleep » 5年前

以下の違いを踏まえた上で

コード:

int v = 5;

auto a = [](){};
auto b = [](){};
auto c = [=](){};
auto d = [&](){};
以下のキャプチャしないとするの違いは何ですか?
あごみつ さんが書きました:

コード:

template <class T, typename... Args, class F,
	class = std::enable_if_t<std::is_convertible<F, T>::value>>
T toFuncPtr(F &&f) // キャプチャしないラムダ式
{
	return f;
}

template <class T, typename... Args, class F,
	class = std::enable_if_t<!std::is_convertible<F, T>::value>>
T toFuncPtr(F f) // キャプチャするラムダ式
{
	static F l = std::forward<F>(f);

	return [](Args... args)
	{
		// 今回は戻り値の型がvoidの時を考慮していない
		return l(std::forward<Args>(args)...);
	};
}

あごみつ
記事: 17
登録日時: 5年前

Re: ラムダ式の戻り値と引数の型を調べたい

#13

投稿記事 by あごみつ » 5年前

sleep さんが書きました:以下の違いを踏まえた上で

コード:

int v = 5;

auto a = [](){};
auto b = [](){};
auto c = [=](){};
auto d = [&](){};
申し訳ないですが、おっしゃる意味がよくわかりません
どの違いでしょうか?

例示されたコードの、a, b, c, d に型以外の違いはないように思えます
c, d は結局ローカルな変数を使ってないのでキャプチャはしてないです
a, b, c, d の型全てに

コード:

using F = void (*)(void);
operator F() const;
が定義されます

しかし、例えば

コード:

int x = 0;
auto f = [&] { return x; };
においては、疑似的に

コード:

int x = 0;

class closure_object
{
public:
	closure_object(int &x) : x(x) {}

	int operator ()(void) { return x; }

private:
	int &x;
} f(x);
とイコールの関係にありますが、こいつには関数ポインタへの暗黙の変換が定義されません
関数ポインタへの暗黙の変換が定義されているかどうかを std::is_convertible により判断しています

sleep

Re: ラムダ式の戻り値と引数の型を調べたい

#14

投稿記事 by sleep » 5年前

あ、戻り値 T だったんですね。今気付きました。
分岐しても、両方とも class のまま返してしまうと いつ関数ポインタに変換するんだろう?
と思ってました。

閉鎖

“C言語何でも質問掲示板” へ戻る