简体   繁体   English

继承成员函数的模板参数推导

[英]Template argument deduction for inherited member function

I struggle with the following issue but are somehow too blinded to see the (probably obvious) solution:我在以下问题上苦苦挣扎,但不知何故无法看到(可能很明显)的解决方案:

I'm writing a class to store invocables with a specific signature (which is given as template arguments to that class).我正在编写一个类来存储具有特定签名(作为该类的模板参数)的调用。 For my MCVE, I left this part out and just assume void() because this part is not the issue.对于我的 MCVE,我忽略了这部分并假设void()因为这部分不是问题。

So, to register an invocable, I use something like this:所以,为了注册一个 invocable,我使用了这样的东西:

template <typename F>
void call(F &&func)
{
  std::cout << "call(f()):\n";
  func();
}

(For the MCVE, I replaced storage by just calling the invocable.) (对于 MCVE,我通过调用 invocable 来替换存储。)

For convenience, I added a second flavor:为方便起见,我添加了第二种口味:

template <typename C>
void call(C *pObj, void(C::*pFunc)())
{
  std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
  (pObj->*pFunc)();
}

to cover objects with member function (matching the required signature).用成员函数覆盖对象(匹配所需的签名)。

This works but only if the member function is defined in the class of the given object itself.这有效,但前提是成员函数是在给定对象本身的类中定义的。 It fails if the member function is inherited.如果成员函数被继承,则失败。

This probably has something to do with templates and inheritance (where the latter is not resolvable while templates are compiled).这可能与模板和继承有关(后者在编译模板时不可解析)。

Complete MCVE:完整的 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. :-(
}

Live Demo on coliru在coliru上进行现场演示

Instead of defining the wrapper function Test::test() , I probably could add something to template <typename C> void call(C*, void(C::*)()) to make this working but I cannot imagine what.而不是定义包装函数Test::test() ,我可能可以在template <typename C> void call(C*, void(C::*)())添加一些东西来使它工作,但我无法想象是什么。

Needless to say that template s are not just my strength…不用说template不只是我的强项......

I already had a look at std::invoke hoping to find inspiration there but this didn't help as well.我已经看过std::invoke希望能在那里找到灵感,但这也无济于事。


Trivia:琐事:

After having struggled with this from Sunday evening until Tuesday morning, I got 3 working solutions in less than 10 minutes.在从周日晚上到周二早上为此苦苦挣扎之后,我在不到 10 分钟的时间内得到了 3 个可行的解决方案。 That's impressive…这很让人佩服…

The problem is that if Test doesn't have test , the type of &Test::test would be void(TestBase::*)() .问题是,如果Test没有test&Test::test的类型将是void(TestBase::*)() Then the template deduction fails because the type deduced for template parameter C from the 1st and 2nd function argument conflicts.然后模板推导失败,因为从第一个和第二个函数参数推导出的模板参数C的类型冲突。

As another solution, you can exclude the 2nd argument pFunc from deduction, with the help of std::type_identity (since C++20) (which takes advantage of non-deduced context ).作为另一种解决方案,您可以在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)();
}

Issue with inherited member is that you might have deduction as继承成员的问题是您可能会扣除

void call(Derived *pObj, void(Base::*pFunc)())

So doesn't match your signature.所以与你的签名不符。

You might fix it with你可以用

template <typename C, typename C2>
void call(C *pObj, void(C2::*pFunc)())
{
  std::cout << "call(C *pObj, void(C::*pFunc)()):\n";
  (pObj->*pFunc)();
}

possibly with SFINAE可能与 SFINAE

or directly或直接

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)();
}

Demo演示

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:我想到的两个最常见的错误:

  1. typo in member function name成员函数名称输入错误
  2. 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):为此,我制作了另一个 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);
}

All three compilers made complaints that I consider as amazingly compact and clean.所有三个编译器都提出了我认为非常紧凑和干净的抱怨。

MSCV 19.27: 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:克++ 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:叮当 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 Compiler Explorer 上的实时演示


The last issue, I did consider – something which I noticed with Qt5 signals where qOverload is provided for:最后一个问题,我确实考虑过——我注意到 Qt5 信号提供了qOverload

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: MSVC v19.27:

Compiler returned: 0

g++ 10.2:克++ 10.2:

Compiler returned: 0

clang 11.0.0:叮当 11.0.0:

Compiler returned: 0

Live Demo on Compiler Explorer Compiler Explorer 上的实时演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM