[英]Type deduction fails with pointer to member method
我有以下模板类作为代理。 它有一个名为call
的方法,它应该用于调用包装对象的方法。 这有问题。 类型扣除失败,我无法理解为什么。
Hudsucker::f
接受一个std::string
然后无论我是否传递一个std::string
或一个const
引用,编译器都可以调用正确的方法。
但是在Hudsucker::g
with的情况下,对于std::string
类型的const
引用,在GCC和Clang两种情况下都会失败。
第一行的GCC错误:
main.cpp:36:28: error: no matching function for call to ‘Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)’
main.cpp:36:28: note: candidate is:
main.cpp:10:10: note: template<class A> void Proxy::call(void (T::*)(A), A) [with A = A; T = Hudsucker]
main.cpp:10:10: note: template argument deduction/substitution failed:
main.cpp:36:28: note: deduced conflicting types for parameter ‘A’ (‘const std::basic_string<char>&’ and ‘std::basic_string<char>’)
特别是这一点很奇怪: no matching function for call to Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)
。 这正是我希望看到工作的签名。
第一行的Clang错误:
main.cpp:36:7: error: no matching member function for call to 'call'
p.call(&Hudsucker::g, s); // <- Compile error
~~^~~~
main.cpp:10:10: note: candidate template ignored: deduced conflicting types for parameter 'A' ('const std::basic_string<char> &' vs. 'std::basic_string<char>')
void call(void (T::*f)(A), A a)
码:
#include <string>
#include <iostream>
template <typename T> class Proxy
{
public:
Proxy(T &o): o_(o) {}
template <typename A>
void call(void (T::*f)(A), A a)
{
(o_.*f)(a);
}
private:
T &o_;
};
class Hudsucker
{
public:
void f(std::string s) {}
void g(std::string const &s) {}
};
int main()
{
Hudsucker h;
Proxy<Hudsucker> p(h);
std::string const s = "For kids, you know.";
std::string const &r = s;
p.call(&Hudsucker::f, s);
p.call(&Hudsucker::f, r);
p.call(&Hudsucker::g, s); // <- Compile error
p.call(&Hudsucker::g, r); // <- Compile error
return 0;
}
你能用这种方式解释为什么类型演绎失败了吗? 有没有办法让这个用const
引用编译?
编译器无法推断出类型A
,因为它具有对比信息。 从成员函数的类型,它将推导A
为std::string const&
,而从第二个参数的类型,它将推导为std::string
。
将您的函数模板更改为允许成员函数的参数和实际提供的参数的不同类型的函数模板,然后SFINAE约束后者可转换为前者:
template <typename A, typename B,
typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
void call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}
如果你想知道为什么从这个函数调用:
std::string const s = "For kids, you know.";
// ...
p.call(&Hudsucker::g, s);
编译器会推断出std::string
,这是因为C ++ 11标准的第14.8.2.1/2段:
如果
P
不是引用类型 :- 如果
A
是数组类型,则使用数组到指针标准转换(4.2)生成的指针类型代替A
进行类型推导; 除此以外,- 如果
A
是函数类型,则使用函数到指针标准转换(4.3)产生的指针类型代替A
进行类型推导; 除此以外,- 如果A是cv限定类型,则类型推导将忽略
A
类型的顶级cv限定符 。
在引用的段落中, P
是你的A
(来自你的函数模板), A
是std::string const
。 这意味着const
中std::string const
的类型推演被忽略。 要更好地了解这一点,请考虑以下更简单的示例:
#include <type_traits>
template<typename T>
void foo(T t)
{
// Does NOT fire!
static_assert(std::is_same<T, int>::value, "!");
}
int main()
{
int const x = 42;
foo(x);
}
考虑第二个函数调用:
std::string const &r = s;
// ...
p.call(&Hudsucker::g, r);
原因是id-expression r
的类型是std::string const
。 由于第5/5段,该引用被删除:
如果表达式最初具有“对
T
引用”类型(8.3.2,8.5.3),则在进行任何进一步分析之前将类型调整为T
表达式指定由引用表示的对象或函数,表达式是左值或x值,具体取决于表达式。
现在我们又回到了与第一个函数调用相同的情况。
正如Mike Vine在评论中指出的那样,在函数调用期间将第二个参数输入到第一个(成员函数)参数时,您可能希望完美地转发第二个参数:
#include <utility> // For std::forward<>()
template <typename A, typename B,
typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
void call(void (T::*f)(A), B&& a)
{
(o_.*f)(std::forward<B>(a));
}
如果你买不起C ++ 11,那么就不允许你对模板参数使用默认参数。 在这种情况下,您可以在返回类型上使用SFINAE约束:
template <typename A, typename B>
typename enable_if<is_convertible<B, A>::value>::type
// ^^^^^^^^^ ^^^^^^^^^^^^^^
// But how about these traits?
call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}
请注意, std::enable_if
和std::is_convertible
不是C ++ 03标准库的一部分。 幸运的是,Boost有自己的enable_if
和is_convertible
版本,所以:
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_convertible.hpp>
template <typename T> class Proxy
{
public:
Proxy(T &o): o_(o) {}
template <typename A, typename B>
typename boost::enable_if<boost::is_convertible<B, A>>::type
call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}
private:
T &o_;
};
请注意, boost::enable_if
接受一个定义value
boolean成员的类型作为其第一个模板参数,而std::enable_if
接受一个布尔值。 Boost中std::enable_if
的等价物是boost::enable_if_c
。
在我看来,一个更简单的解决方案是将两个参数中的一个排除在尝试推导A之外,第二个是更好的候选者:
template <typename A>
void call(void (T::*f)(A), typename std::identity<A>::type a)
{
(o_.*f)(a);
}
如果您的类型特征中没有std::identity
,请使用以下代码:
template <typename T>
struct identity { typedef T type; };
这就是为什么这样做的原因:编译器不能从第二个参数中推导出A,因为它只是一个嵌套类型的模板参数。 基本上,它不能将任何传入类型与something_that_contains_A :: type进行模式匹配 - 由于模板特化,它无法对左侧定义中的参数进行反向工程。 最终结果是第二个参数是“未受限制的上下文”。 编译器不会尝试从那里推导出A.
这使得第一个参数成为唯一可以推导出A的地方。 只有一个A的演绎结果,它不含糊,演绎成功。 然后,编译器继续将推导结果替换为使用A的每个位置,包括第二个参数。
您只需要在main中调用模板函数时将模板参数传递给模板函数。
int main()
{
Hudsucker h;
Proxy<Hudsucker> p(h);
std::string const s = "For kids, you know.";
std::string const &r = s;
p.call(&Hudsucker::f, s);
p.call(&Hudsucker::f, r);
//just add template argument to template function call !!!
p.call< const std::string & > (&Hudsucker::g, s); // <- NO Compile error !!!!
p.call< const std::string & > (&Hudsucker::g, r); // <- NO Compile error !!!**
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.