在模板基类中为继承类中的可选覆盖生成虚拟方法

Posted

技术标签:

【中文标题】在模板基类中为继承类中的可选覆盖生成虚拟方法【英文标题】:Generating virtual methods in template base class for optional override in inheriting class 【发布时间】:2020-02-20 16:39:50 【问题描述】:

我想要的是一个模板类,给定元组并用作基类,为元组中的每种元素类型提供具有默认行为的方法。这些方法应该是虚拟的,以便它们可以在继承类中被覆盖。

下面的代码正是这样做的:

#include <tuple>
#include <iostream>

struct A ;
struct B ;
struct C ;

template <typename Class, uint16_t tag>
struct def 
        using message_type = Class;
        static constexpr uint16_t class_tag = tag;
;

// (3) adding "constexpr" or "const" causes compilation failure
auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

template <typename T> // (1)
struct base_handler_t 
        virtual void h(T const& t)  std::cout << "base_handler_t\n"; 
;

template <typename ...Ts> // (2) - adding "const" to "std::tuple<Ts...>" in line below makes code work again if "t" is constant
struct base_handler_t<std::tuple<Ts...>> : public base_handler_t<typename Ts::message_type>...
        using base_handler_t<typename Ts::message_type>::h...;
;

struct service_t : public base_handler_t<decltype(t)> 
        using base_handler_t<decltype(t)>::h;
        void h(B const & b) 
                std::cout << "service_t\n";
        
;

int main() 
        service_t n;
        n.h(A());
        n.h(B());

在找到破坏代码的准确和最小示例后进行编辑:

上面的代码在按原样输入时可以正常工作,但是如果注释(3)(关于将constexpr 添加到t 的定义)下面的行更改为:

const auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

constexpr auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

代码编译失败。编译器声称:

x.cc: In function ‘int main()’:
x.cc:35:16: error: no matching function for call to ‘service_t::h(A)’
   35 |         n.h(A());
      |                ^
x.cc:28:14: note: candidate: ‘void service_t::h(const B&)’
   28 |         void h(B const & b) 
      |              ^
x.cc:28:26: note:   no known conversion for argument 1 from ‘A’ to ‘const B&’
   28 |         void h(B const & b) 
      |                ~~~~~~~~~~^
x.cc:18:22: note: candidate: ‘void base_handler_t<T>::h(const T&) [with T = const std::tuple<def<A, 0>, def<B, 1>, def<C, 2> >]’
   18 |         virtual void h(T const& t)  std::cout << "base_handler_t\n"; 
      |                      ^
x.cc:18:33: note:   no known conversion for argument 1 from ‘A’ to ‘const std::tuple<def<A, 0>, def<B, 1>, def<C, 2> >&’
   18 |         virtual void h(T const& t)  std::cout << "base_handler_t\n"; 
      |                        ~~~~~~~~~^

我假设当涉及到模板时,如果从常量或变量中提取类型并没有真正的区别。

在下面(2)行更改后代码开始工作:

struct base_handler_t<std::tuple<Ts...>> : ...

struct base_handler_t<const std::tuple<Ts...>> : ...

为什么会这样?是因为std::tuple&lt;Ts...&gt;const std::tuple&lt;Ts...&gt; 不完全匹配吗?管辖此案的具体规则是什么?

提前致谢。

原始请求帮助:

在原始代码中,每种类型(上面示例中的 A、B 和 C)的基本方法都是在“service_t”类中手动定义的。我试图完全按照上面的例子解决这个问题。只要所有方法都存在,代码仍然可以正常工作。一旦我注释掉单个方法,我就会收到错误,即没有可以调用的匹配方法,然后是可能匹配的列表。具有参数std::tuple&lt;def&lt;.... 的方法匹配等等 - 似乎在我上面的 sn-p 中一切正常(因此为每个元组元素类型生成一个方法),有 something来自匹配模板 (2) 的更大代码库,而是使用模板 (1)。

我想听听任何的想法,为什么这会失败。提前致谢。

【问题讨论】:

你能提供你失败的代码吗? 特别是,以某种方式说明“只要我注释掉单个方法”的意思。 在我们看不到的代码中很难找到错误。 是的,我知道很难在错误代码中找到你看不到的错误,这就是我寻求建议的原因。原始代码要大得多并且是专有的,这就是为什么我试图提供一个 sn-p,令我沮丧的是,它可以工作。我将尝试复制我的代码,以便尽可能地类似于原始代码。 您已经在这个网站上活跃了五年。没有人告诉你minimal complete examples? 【参考方案1】:

auto t = std::make_tuple(def&lt;A, 0&gt;(), def&lt;B, 1&gt;(), def&lt;C, 2&gt;());

decltype(t)std::tuple&lt;def&lt;A, 0&gt;, def&lt;B, 1&gt;, def&lt;C, 2&gt;&gt;

如果tconst 合格:const auto t = /*..*/,那么,decltype(t)const std::tuple&lt;def&lt;A, 0&gt;, def&lt;B, 1&gt;, def&lt;C, 2&gt;&gt;

所以对于base_handler_t&lt;decltype(t)&gt;base_handler_t&lt;const std::tuple&lt;def&lt;A, 0&gt;, def&lt;B, 1&gt;, def&lt;C, 2&gt;&gt;&gt; 只匹配主模板定义,而不是你的专业。

您可以改用 base_handler_t&lt;std::remove_const_t&lt;decltype(t)&gt;&gt;base_handler_t&lt;std::decay_t&lt;decltype(t)&gt;&gt;(删除引用,然后删除 cv 限定符)

【讨论】:

好的,我明白了。我只是认为模板不会查看传递类型的 CV 限定(或任何你称之为的),因为例如您不能使用完整的非常量类型声明模板。这完全回答了我的问题,谢谢。 @JędrzejDudkiewicz:你如何实现/使用像std::is_conststd::remove_const这样的特征…… 这仍然适用于我的这个想象世界,因为即使类型是 CV 限定的(并且可以是指针、引用等),如果它比匹配更好,它也会匹配没有这些的类型包罗万象,当作为简单模板 传递时,它会保留这些属性,以便以后可以使用 - 但是,是的,我想这不是经过深思熟虑的。永远感谢您的解释和快速课程:)

以上是关于在模板基类中为继承类中的可选覆盖生成虚拟方法的主要内容,如果未能解决你的问题,请参考以下文章

抽象派生类中的抽象方法覆盖/实现抽象基类的抽象或具体方法。如何以及为啥?

如何覆盖继承的嵌套类中存在的虚拟方法

给定基类方法的编译时覆盖

为啥基类指针指向基类中的纯虚方法,而不是派生类中的覆盖方法?

父类中的方法被覆盖以及子类调用父类覆盖的方法

如何从基类中的函数调用重载函数?