オブザーバーパターンを再作成しようとしています。ここで、パラメーターをオブザーバーの特定のメンバー関数に完全に転送できます。
複数のオーバーライドがあるメンバー関数のアドレスを渡そうとすると、引数に基づいて正しいメンバー関数を推定できません。
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Class>
struct observer_list
{
template<typename Ret, typename... Args, typename... UArgs>
void call(Ret (Class::*func)(Args...), UArgs&&... args)
{
for (auto obj : _observers)
{
(obj->*func)(std::forward<UArgs>(args)...);
}
}
std::vector<Class*> _observers;
};
struct foo
{
void func(const std::string& s)
{
std::cout << this << ": " << s << std::endl;
}
void func(const double d)
{
std::cout << this << ": " << d << std::endl;
}
};
int main()
{
observer_list<foo> l;
foo f1, f2;
l._observers = { &f1, &f2 };
l.call(&foo::func, "hello");
l.call(&foo::func, 0.5);
return 0;
}
これはtemplate argument deduction/substitution failed
でコンパイルできません。
Args...
とUArgs...
があることに注意してください。これは、関数シグネチャのタイプと必ずしも同じタイプではないが、上記のタイプに変換可能なパラメーターを渡すことができる必要があるためです。
std::enable_if<std::is_convertible<Args, UArgs>>
呼び出しを使用して曖昧さをなくすことができると思っていましたが、可変テンプレートテンプレートパックでこれを実行できるとは思いませんか?
テンプレート引数の控除をここで機能させるにはどうすればよいですか?
問題はここにあります:
l.call(&foo::func, "hello");
l.call(&foo::func, 0.5);
どちらの行でも、コンパイラーはどのfoo::func
を参照しているかがわかりません。したがって、キャストを介して欠落しているタイプ情報(つまり、foo:func
のタイプ)を提供して、明確にする必要があります。
l.call(static_cast<void (foo::*)(const std::string&)>(&foo::func), "hello");
l.call(static_cast<void (foo::*)(const double )>(&foo::func), 0.5);
あるいは、コンパイラーが推定できないテンプレート引数を提供し、func
のタイプを定義することができます。
l.call<void, const std::string&>(&foo::func, "hello");
l.call<void, double >(&foo::func, 0.5);
上記のconst double
ではなくdouble
を使用する必要があることに注意してください。その理由は、一般にdouble
とconst double
は2つの異なる型であるためです。ただし、double
とconst double
が関数の引数として同じ型であると見なされる状況が1つあります。例えば、
void bar(const double);
void bar(double);
2つの異なるオーバーロードではありませんが、実際には同じ関数です。