为什么在这种情况下无法推导模板参数?

16
有人能解释一下为什么编译器(g++,visual c++)在这种情况下无法推导出模板参数吗?
struct MyClass
{
    void Foo(int x)&  {}
    void Foo(int x)&& {}
};

template<typename T>
void CallFoo(void(T::*func)(int)&)
{
    //create instance and call func
}

int main()
{
   CallFoo(&MyClass::Foo); // Fails to deduce T
}

为什么编译器无法将T推断为MyClass?这只会发生在使用ref限定符重载的方法中。如果一个方法是通过const或参数类型进行重载的,一切都正常。 似乎只有Clang可以在这种情况下推断T。

很好,clang 愉快地接受它。 或许这里涉及的问题 here 对于 gcc 或 vc++ 仍未解决? - jaggedSpire
@jaggedSpire 我越看越困惑。我不确定它是否实际上有助于函数类型,还是仅描述了 *this 的类型。 - NathanOliver
2
@NathanOliver [dcl.fct]/6: "返回类型、参数类型列表、ref-限定符和cv-限定符序列……是函数类型的一部分。",而clang将其视为类型的一部分 - jaggedSpire
2
g++7 似乎可以编译这个。 - Holt
2
@NathanOliver 基本上,&是左值引用限定符,而&&是右值引用限定符(绑定到临时对象); 在他的例子中,MyClass m; m.Foo(3);将调用顶部的函数,而MyClass{}.Foo(3);将调用底部的函数。它们作用于隐式对象参数;左值引用限定符绑定到左值引用,右值引用限定符绑定到右值引用(没有这两者的函数将参数作为左值引用,但让它绑定到任何一个)。请注意,它们实际上不会改变*this的类型。 - Justin Time - Reinstate Monica
显示剩余4条评论
2个回答

1

总结评论中的讨论: 将引用限定的成员函数作为模板参数的支持是一些编译器相对较新的特性。然而,大多数编译器的最新版本都可以编译这样的代码。


例如:

#include <iostream>

struct MyClass
{
    void Foo(int) const &
    {
        std::cout << "calling: void Foo(int) const &\n";
    }
    void Foo(int) const &&
    {
        std::cout << "calling: void Foo(int) const &&\n";
    }
};

template<typename T>
void CallFoo_lvalue(void (T::*foo)(int) const &)
{
    T temp;
    (temp.*foo)(0);
}

template<typename T>
void CallFoo_rvalue(void (T::*foo)(int) const &&)
{
    (T{}.*foo)(0);
}

int main()
{
   CallFoo_lvalue(&MyClass::Foo);
   CallFoo_rvalue(&MyClass::Foo);
}

将使用以下编译器进行编译:

  • gcc(从7.0.0版本开始可用)
  • Visual C++(需要v19.10.24903.0版本及以上)

生成以下输出:

calling: void Foo(int) const &
calling: void Foo(int) const &&

对于那些想知道&&&的用途的人:这是来自@JustinTime的引语:

基本上,&是左值引用限定符,&&是右值引用限定符(绑定到临时对象);在他的例子中,MyClass m; m.Foo(3);将调用顶部的一个,而MyClass{}.Foo(3);将调用底部的一个。它们作用于隐式对象参数;左值引用限定符绑定到左值引用,右值引用限定符绑定到右值引用(没有参数的函数将其作为左值引用传递,但让它绑定到任何一个)。请注意,它们实际上不会改变*this的类型。


0
如果您希望模板绑定到不同的引用类型,您需要使用通用引用。
template<typename T>
void func(T&& arg)
{
    other_func(std::forward<T>(arg));
}

这将绑定到左值或右值引用。std :: forward将确保在后续调用中使用适当的引用。我不确定如何将双“&”符号适配到您的代码中,但也许只需要

template<typename T>
void CallFoo(void(T::*func)(int)&&)

也许更好的选择是

template<typename func_t>
void CallFoo(func_t && f)
{
    call(std::forward<func_t>(f));
}

template<typename func_t>
void call(typename std::remove_reference<func_t> & f)
{
    f();
}

template<typename func_t>
void call(typename std::remove_reference<func_t> && f)
{
    f();
}

或者您需要调用函数指针的任何语法,比如*f();

如果您想要传递参数:

template<typename func_t, typename ... args_t>
void CallFoo(func_t && f, args_t && ... args)
{
    call(std::forward<func_t>(f), std::forward<args_t>(args)...);
}

template<typename func_t, typename ... args_t>
void call(typename std::remove_reference<func_t> & f, args_t && ... args)
{
    f(std::forward<args_t>(args)...);
}

template<typename func_t, typename ... args_t>
void call(typename std::remove_reference<func_t> && f, args_t && ... args)
{
    f(std::forward<args_t>(args)...);
}

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