模板形参有二义性:无法推断模板实参

Template parameter is ambiguous: could not deduce template argument

本文关键字:实参 形参 二义性      更新时间:2023-10-16

我正在做一些包装,看起来像这样:

#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());
}

我得到这个错误:

  • Apply:没有找到匹配的重载函数。
  • void Apply(void (__thiscall T::* )(Value),T *,Value):模板参数Value有歧义,可以是intconst int &
  • void Apply(void (__thiscall T::* )(Value),T *,Value):无法从const int中推断出Value的模板参数。

所以我得到它,它来自模板参数演绎,但我不明白如何。为什么Value 两次都求值为const int& ?

为什么失败

目前,模板形参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()

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

你可能想用两个单独的模板参数来代替Value(一个用于函数类型的参数,一个用于实际参数类型),当cb不能被v调用时,使用SFINAE来禁用Apply(或static_assert用于硬错误)。