消除作为模板参数传递的重载成员函数指针的歧义

时间:2013-07-26 06:35:18

标签: c++ templates c++11 typetraits

我正在尝试重新创建观察者模式,我可以将参数完美地转发给观察者的给定成员函数。

如果我尝试传递成员函数的地址多次覆盖,则无法根据参数推断出正确的成员函数。

#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>>调用来消除歧义,但我不相信我可以使用可变参数模板参数包执行此操作?

如何让模板参数扣除在这里工作?

1 个答案:

答案 0 :(得分:43)

问题在于:

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);

请注意,您必须使用上面的double而不是const double。原因是通常doubleconst double是两种不同的类型。但是,有一种情况是doubleconst double被认为是相同的类型:作为函数参数。例如,

void bar(const double);
void bar(double);

不是两个不同的重载,但实际上是相同的功能。