9

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

#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): 无法推断Valuefrom 的模板参数const int

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

4

2 回答 2

13

为什么会失败

目前,模板参数Value在调用的两个不同位置推导出来Apply:从指向成员函数参数的指针和从最后一个参数。从&Foo::MyFuncValue推导出为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>所以在模板推导过程中,从指向成员函数的指针推导出来(和以前一样),现在我们只需将其插入到类型 forValue中。int const&v

或者,您可以选择推断cb作为它自己的模板参数。在这一点上,Apply()只是减少到std::invoke()

于 2016-11-08T22:05:16.897 回答
3

表达式f.GetValue()是类型的左值const int。当 this 按值传递时,模板参数推导推导出类型int。一般来说,ValueValue v不会产生引用或具有顶级 cv 限定的类型

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

于 2016-11-08T21:51:31.427 回答