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

11

我正在编写一种类似于这样的封装器:

#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&呢?


这个例子是问题的重新创建,但不代表我正在处理的代码本身。 - HiroshimaCC
2个回答

14

为什么会失败

目前,在调用Apply时,模板参数Value在两个不同的位置进行推导:从成员函数指针参数和最后一个参数中进行推导。从&Foo::MyFunc中,Value被推导为int const&。从f.GetValue()中,Value被推导为int。这是因为引用和顶层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()

谢谢你的建议,我实际上使用了 std::decay 来完成它。 - HiroshimaCC
@AlexandreBourlon 在这里使用decay是一个不好的选择 - 如果Value是一个引用类型,那么现在你将会传递参数的副本到cb中,这不是你想要的。 - Barry
你的解决方案不也是这样吗?我一开始也是这么想的...现在我正在使用std::decay_t<ValueType> const&,所以类型会失去引用和cv限定符(如果有的话),然后我强制将const&应用于参数。我知道这不是直接等同于调用点传递的类型,但对于我的用例来说似乎已经足够好了。不过我仍然愿意听取更好的建议。 - HiroshimaCC
@AlexandreBourlon 不是这样的。考虑一下如果“Value”是一个非const左值引用会发生什么。你将通过const左值引用来获取你的参数并尝试传递它。 - Barry
所以我再进一步调查了一下非推断上下文,找到了这个其他的SO问题:https://dev59.com/ml8e5IYBdhLWcg3w6NyW。在这种情况下,你认为`std::common_type`怎么样? - HiroshimaCC
@AlexandreBourlon common_type_t<T> 就是 decay_t<T>,所以它也有同样的问题。 - Barry

4
表达式f.GetValue()是类型为const int的lvalue。当以值传递时,模板参数推导会推断出类型int。通常,从Value v中推导出Value永远不会产生引用或具有顶层cv限定符的类型。
您可能需要在Value的位置上分别放置两个单独的模板参数(一个用于函数类型的参数,另一个用于实际参数类型),并使用SFINAE在cb不能与v调用时禁用Apply(或使用static_assert进行硬错误)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接