After having received two solutions, I was spoiled for choice.
Two justify this, I considered the case of an editors mistake (as I remembered with scare of the flood of complaints the compiler spits out sometimes for the slightest typos in combination with templates).
The two most common mistakes which came in my mind:
- typo in member function name
- member function with wrong signature used.
For this I made another MCVE which I tested against MSVC (my primary platform), and g++ and clang (possible alternatives, used for cross-checking the code):
#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);
}
All three compilers made complaints that I consider as amazingly compact and clean.
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'
g++ 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)())
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
clang 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)())
^
Live Demo on Compiler Explorer
The last issue, I did consider – something which I noticed with Qt5 signals where qOverload is provided for:
Are these solutions capable to select the right member function out of multiple candidates with distinct signatures:
#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);
}
Yes.
MSVC v19.27:
Compiler returned: 0
g++ 10.2:
Compiler returned: 0
clang 11.0.0:
Compiler returned: 0
Live Demo on Compiler Explorer