[英]Template argument deduction for inherited member function
我在以下问题上苦苦挣扎,但不知何故无法看到(可能很明显)的解决方案:
我正在编写一个类来存储具有特定签名(作为该类的模板参数)的调用。 对于我的 MCVE,我忽略了这部分并假设void()
因为这部分不是问题。
所以,为了注册一个 invocable,我使用了这样的东西:
template <typename F>
void call(F &&func)
{
std::cout << "call(f()):\n";
func();
}
(对于 MCVE,我通过调用 invocable 来替换存储。)
为方便起见,我添加了第二种口味:
template <typename C>
void call(C *pObj, void(C::*pFunc)())
{
std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
(pObj->*pFunc)();
}
用成员函数覆盖对象(匹配所需的签名)。
这有效,但前提是成员函数是在给定对象本身的类中定义的。 如果成员函数被继承,则失败。
这可能与模板和继承有关(后者在编译模板时不可解析)。
完整的 MCVE:
#include <iostream>
template <typename F>
void call(F &&func)
{
std::cout << "call(f()):\n";
func();
}
template <typename C>
void call(C *pObj, void(C::*pFunc)())
{
std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
(pObj->*pFunc)();
}
// Test:
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
void test() { std::cout << "test()\n"; }
struct TestBase {
void test() { std::cout << "TestBase::test()\n"; }
};
struct Test: TestBase {
#ifdef FIX
void test() { TestBase::test(); }
#endif // FIX
};
int main()
{
DEBUG(call(&test));
DEBUG(Test obj);
DEBUG(call((TestBase*)&obj, &TestBase::test));
DEBUG(call([&]() { obj.test(); }));
DEBUG(call<Test>(&obj, &Test::test));
DEBUG(call(&obj, &Test::test)); // <== PROBLEM! Doesn't work without -DFIX. :-(
}
而不是定义包装函数Test::test()
,我可能可以在template <typename C> void call(C*, void(C::*)())
添加一些东西来使它工作,但我无法想象是什么。
不用说template
不只是我的强项......
我已经看过std::invoke希望能在那里找到灵感,但这也无济于事。
琐事:
在从周日晚上到周二早上为此苦苦挣扎之后,我在不到 10 分钟的时间内得到了 3 个可行的解决方案。 这很让人佩服…
问题是,如果Test
没有test
, &Test::test
的类型将是void(TestBase::*)()
。 然后模板推导失败,因为从第一个和第二个函数参数推导出的模板参数C
的类型冲突。
作为另一种解决方案,您可以在std::type_identity
(C++20 起)(利用非推导上下文)的帮助下从推导中排除第二个参数pFunc
。
template <typename C>
void call(C *pObj, void(std::type_identity_t<C>::*pFunc)())
{
std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
(pObj->*pFunc)();
}
继承成员的问题是您可能会扣除
void call(Derived *pObj, void(Base::*pFunc)())
所以与你的签名不符。
你可以用
template <typename C, typename C2>
void call(C *pObj, void(C2::*pFunc)())
{
std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
(pObj->*pFunc)();
}
可能与 SFINAE
或直接
template <typename C, typename Member>
auto call(C *pObj, Member m) -> decltype((pObj->*m)(), void())
{
std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
(pObj->*m)();
}
在收到两种解决方案后,我被宠坏了。
有两个理由可以证明这一点,我考虑了编辑器错误的情况(因为我记得编译器有时会因为与模板结合的最轻微的拼写错误而吐出大量投诉)。
我想到的两个最常见的错误:
为此,我制作了另一个 MCVE,我针对 MSVC(我的主要平台)以及g++
和clang
(可能的替代方案,用于交叉检查代码)进行了测试:
#include<iostream>
template <typename C, typename CF>
void call1(C *pObj, void(CF::*pFunc)())
{
(pObj->*pFunc)();
}
template< class T >
struct type_identity {
using type = T;
};
template< class T >
using type_identity_t = typename type_identity<T>::type;
template <typename C>
void call2(C *pObj, void(type_identity_t<C>::*pFunc)())
{
(pObj->*pFunc)();
}
// Test:
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
void test() { std::cout << "test()\n"; }
struct TestBase {
void test() { std::cout << "TestBase::test()\n"; }
void other(int) { std::cout << "TestBase::other()\n"; }
};
struct Test: TestBase {
};
int main()
{
Test obj;
// wrong member function
call1(&obj, &Test::testit);
call2(&obj, &Test::testit);
// member function with wrong signature
call1(&obj, &Test::other);
call2(&obj, &Test::other);
}
所有三个编译器都提出了我认为非常紧凑和干净的抱怨。
MSCV 19.27:
<source>(41): error C2039: 'testit': is not a member of 'Test'
<source>(34): note: see declaration of 'Test'
<source>(41): error C2065: 'testit': undeclared identifier
<source>(42): error C2039: 'testit': is not a member of 'Test'
<source>(34): note: see declaration of 'Test'
<source>(42): error C2065: 'testit': undeclared identifier
<source>(44): error C2672: 'call1': no matching overloaded function found
<source>(44): error C2784: 'void call1(C *,void (__cdecl CF::* )(void))': could not deduce template argument for 'void (__cdecl CF::* )(void)' from 'void (__cdecl TestBase::* )(int)'
<source>(4): note: see declaration of 'call1'
<source>(45): error C2664: 'void call2<Test>(C *,void (__cdecl Test::* )(void))': cannot convert argument 2 from 'void (__cdecl TestBase::* )(int)' to 'void (__cdecl Test::* )(void)'
with
[
C=Test
]
<source>(45): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
<source>(18): note: see declaration of 'call2'
克++ 10.2:
<source>: In function 'int main()':
<source>:41:22: error: 'testit' is not a member of 'Test'
41 | call1(&obj, &Test::testit);
| ^~~~~~
<source>:42:22: error: 'testit' is not a member of 'Test'
42 | call2(&obj, &Test::testit);
| ^~~~~~
<source>:44:27: error: no matching function for call to 'call1(Test*, void (TestBase::*)(int))'
44 | call1(&obj, &Test::other);
| ^
<source>:4:6: note: candidate: 'template<class C, class CF> void call1(C*, void (CF::*)())'
4 | void call1(C *pObj, void(CF::*pFunc)())
| ^~~~~
<source>:4:6: note: template argument deduction/substitution failed:
<source>:44:27: note: candidate expects 1 argument, 2 provided
44 | call1(&obj, &Test::other);
| ^
<source>:45:15: error: cannot convert 'void (TestBase::*)(int)' to 'void (Test::*)()'
45 | call2(&obj, &Test::other);
| ^~~~~~~~~~~~
| |
| void (TestBase::*)(int)
<source>:18:21: note: initializing argument 2 of 'void call2(C*, void (type_identity<T>::type::*)()) [with C = Test; typename type_identity<T>::type = Test; type_identity_t<C> = Test]'
18 | void call2(C *pObj, void(type_identity_t<C>::*pFunc)())
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
叮当 11.0.0:
<source>:41:16: error: no member named 'testit' in 'Test'; did you mean '::Test::test'?
call1(&obj, &Test::testit);
^~~~~~~~~~~~
::Test::test
<source>:30:8: note: '::Test::test' declared here
void test() { std::cout << "TestBase::test()\n"; }
^
<source>:42:16: error: no member named 'testit' in 'Test'; did you mean '::Test::test'?
call2(&obj, &Test::testit);
^~~~~~~~~~~~
::Test::test
<source>:30:8: note: '::Test::test' declared here
void test() { std::cout << "TestBase::test()\n"; }
^
<source>:44:3: error: no matching function for call to 'call1'
call1(&obj, &Test::other);
^~~~~
<source>:4:6: note: candidate template ignored: failed template argument deduction
void call1(C *pObj, void(CF::*pFunc)())
^
<source>:45:3: error: no matching function for call to 'call2'
call2(&obj, &Test::other);
^~~~~
<source>:18:6: note: candidate function [with C = Test] not viable: no known conversion from 'void (TestBase::*)(int)' to 'void (type_identity_t<Test>::*)()' for 2nd argument
void call2(C *pObj, void(type_identity_t<C>::*pFunc)())
^
最后一个问题,我确实考虑过——我注意到 Qt5 信号提供了qOverload :
这些解决方案是否能够从具有不同签名的多个候选函数中选择正确的成员函数:
#include<iostream>
template <typename C, typename CF>
void call1(C *pObj, void(CF::*pFunc)())
{
(pObj->*pFunc)();
}
template< class T >
struct type_identity {
using type = T;
};
template< class T >
using type_identity_t = typename type_identity<T>::type;
template <typename C>
void call2(C *pObj, void(type_identity_t<C>::*pFunc)())
{
(pObj->*pFunc)();
}
// Test:
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
void test() { std::cout << "test()\n"; }
struct TestBase {
void test() { std::cout << "TestBase::test()\n"; }
void test(int) { std::cout << "TestBase::test(int)\n"; }
};
struct Test: TestBase {
};
int main()
{
Test obj;
// choose member function with right signature
call1(&obj, &Test::test);
call2(&obj, &Test::test);
}
是的。
MSVC v19.27:
Compiler returned: 0
克++ 10.2:
Compiler returned: 0
叮当 11.0.0:
Compiler returned: 0
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.