模板参数不明确:无法推断模板参数

Template parameter is ambiguous: could not deduce template argument

我正在做类似这样的包装:

#include <iostream>

template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, Value v)
{
    (obj->*cb)(v);
}

class Foo
{
public:
    void MyFunc(const int& i)
    {
        std::cout << i << std::endl;
    }

    const int& GetValue()
    {
        return i_;
    }

private:
    int i_ = 14;
};

int main()
{
    Foo f;
    Apply(&Foo::MyFunc, &f, f.GetValue());
}

我收到这个错误:

所以我知道它来自模板参数推导,但是我不明白如何。为什么 Value 不会 两次都计算为 const int&

表达式 f.GetValue() 是类型 const int 的左值。当它按值传递时,模板参数推导推导类型 int。通常,从 Value v 推导 Value 永远不会 产生引用或具有顶级 cv 限定的类型。

您可能希望用两个单独的模板参数代替 Value(一个用于函数类型的参数,一个用于实际参数类型)并使用 SFINAE 禁用 Apply cb 不能用 v 调用(或 static_assert 硬错误)。

为什么会失败

目前,模板参数 Value 在调用 Apply 的两个不同位置推导出来:从指向成员函数参数的指针和从最后一个参数。从&Foo::MyFunc推导出Valueint const&。从f.GetValue()推导出Valueint。这是因为参考和顶级 cv 限定符被删除以进行模板推导。由于参数 Value 的这两个推导不同,推导失败 - 从重载集中删除 Apply(),结果我们没有可行的重载。

如何修复

问题是 Value 是在两个不同的地方推导的,所以让我们防止这种情况发生。一种方法是将其中一个用途包装在非推导上下文中:

template <class T> struct non_deduced { using type = T; };
template <class T> using non_deduced_t = typename non_deduced<T>::type;

template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, non_deduced_t<Value> v)
{
    (obj->*cb)(v);
}

最后一个参数 v 属于 non_deduced_t<Value> 类型,顾名思义,它是一个非推导上下文。因此在模板推导过程中,Value 从指向成员函数的指针推导为 int const&(和以前一样),现在我们只需将其插入 v 的类型。

或者,您可以选择将 cb 推导为它自己的模板参数。此时 Apply() 刚好减少到 std::invoke()