非静态模板成员:可能吗?

Posted

技术标签:

【中文标题】非静态模板成员:可能吗?【英文标题】:non-static template member : possible? 【发布时间】:2017-10-16 16:40:13 【问题描述】:

是否可以在类中创建非静态模板字段? 如果没有,如何解决?

此类字段应在编译时根据需要创建。

示例

我有很多B-class,比如B1,B2,B3(实际上,它们有更有意义的名字。)

我想创建一个类D,它具有非静态模板函数add<BX>(),每次我调用它时都必须counter++,对于每个人BX,对于一个D 的某个实例。(在实际情况下,它做了更复杂的事情。)

这是一个有效的demo 来实现它。 可悲的是,我目前必须在D 中逐个(B1B2B3)对每个BX 进行硬编码:-

class B1;class B2;class B3;
class Counter
    public: int counter=0;
;
template<class BX>class Tag;
class D
    Counter countB1;
    Counter countB2;
    Counter countB3;
    public: template<class BX> void add()  
        add_(Tag<BX>());
    
    private:
    void add_(Tag<B1>) countB1.counter++;
    void add_(Tag<B2>) countB2.counter++;
    void add_(Tag<B3>) countB3.counter++;
    public: template<class BX> int get()
        return get_(Tag<BX>());
    
    private:
    int get_(Tag<B1>)  return countB1.counter;
    int get_(Tag<B2>)  return countB2.counter;
    int get_(Tag<B3>)  return countB3.counter;
;

这里是用法。注意D 的每个实例都有自己的counter:-

int main() 
    D d1;
    d1.add<B2>();   d1.add<B2>();   d1.add<B3>();
    std::cout<<d1.get<B1>()<<" "<<d1.get<B2>()<<" "<<d1.get<B3>()<<"\n";
    //^ print 0 2 1  
    D d2;
    d2.add<B1>();
    std::cout<<d2.get<B1>()<<" "<<d2.get<B2>()<<" "<<d2.get<B3>()<<"\n";
    //^ print 1 0 0  (not 1 2 1)
    return 0;

我的梦想是:-

class D
    Counter<BX> countBX; //???
    public: template<class BX> void add()  
         Counter<BX>::getNonStaticInstance(this).counter++; //???
    
    public: template<class BX> int get()
        return Counter<BX>::getNonStaticInstance(this).counter; //???
    
;

如果countBX 是静态的,我知道该怎么做,但对于非静态来说,这似乎是不可能的。

【问题讨论】:

【参考方案1】:

您不需要RTTI 来解决这个问题,也不需要std::map,它们非常昂贵(特别是RTTI)。可变参数模板和继承可以为您解决这个问题:

class B1 ; class B2 ; class B3 ;

template<typename T>
class Counter 
  public:
    int counter = 0;
;

template<class... BXs>
class D : public Counter<BXs>... 
  public:
    template<typename B>
    void add() 
      Counter<B>::counter++;
    

    template<typename B>
    int get() 
      return Counter<B>::counter;
    
;

这与您真正想要的非常接近(顺便说一句,您走在正确的轨道上)。

【讨论】:

它是如此简单......非常好的和优雅的解决方案。 只是给自己的一个提示:在这个解决方案中,我必须手动将所有 BX 放入 D 例如D&lt;B1,B2,B3&gt; d1; 有点像 AndyG 的解决方案。 @javaLover 不幸的是。【参考方案2】:

使用索引和 RTTI 的 std::map std::unordered_map(Yakk 的建议;谢谢)?

#include <map>
#include <iostream>
#include <typeindex>

class B1 ;
class B2 ;
class B3 ;

class D
 
   private:
      std::unordered_map<std::type_index, std::size_t> bxMap;

   public:
      template <typename BX>
      void add ()
        ++ bxMap[std::type_index(typeid(BX))]; 

      template <typename BX>
      int get ()
        return bxMap[std::type_index(typeid(BX))]; 
 ;

int main ()
 
   D d1;
   d1.add<B2>();    d1.add<B2>();   d1.add<B3>();
   std::cout<<d1.get<B1>()<<" "<<d1.get<B2>()<<" "<<d1.get<B3>()<<"\n";
   //^ print 0 2 1
   D d2;
   d2.add<B1>();
   std::cout<<d2.get<B1>()<<" "<<d2.get<B2>()<<" "<<d2.get<B3>()<<"\n";
   //^ print 1 0 0
   return 0;
 

【讨论】:

你关心那张地图的顺序吗? Yakk - 不,我想我不知道。你的意思是更好std::unordered_map 出色的 RTTI 解决方案! 它比 OP 慢 2 倍,不过 (-O2)。 OP 的代码(~ 60K 滴答声):coliru.stacked-crooked.com/a/f3d9885b523dc9a5 这个解决方案(~120K 滴答声):coliru.stacked-crooked.com/a/4fe76fc73507a306。这还不错 - 我只想指出。 @javaLover 它做的事情与 OP 不同:OP 代码处理固定的类型集合;这处理了无限的类型集合。另请注意,该操作是微不足道的(++),而在“真实”情况下,该操作将更加昂贵(因此比率会下降)。而max66,是的,map 真的是ordered_map:如果你不需要订单,考虑unordered_map【参考方案3】:

不幸的是,在我们对标准进行反思之前,没有简单的方法可以迭代类的成员。

在此之前的解决方案要么涉及自己实现反射(很难并且经常使用带有自身问题的宏,例如可调试性),要么将您的类型包装成另一种类型(更容易)。

我们可以使用具有std::array 计数器的基类来做到这一点,每个BX 一个:

template<class... Bs>
struct Base

    std::array<Counter, sizeof...(Bs)> counters;
    // ... more on this later
;

然后我们的D 类可以从中派生并获取它需要的计数器:

struct D :  Base<B1, B2, B3> /*...*/;

接下来我们要做的是在基类中实现一个IndexOf 函数,它允许我们将一个类型(B1B2B3 之一)转换为一个索引。

我们可以使用类型特征和折叠表达式来做到这一点:

template<class T>
static constexpr int IndexOf()

    // find index of T in Bs...
    int toReturn = 0;
    int index = 0;
    (..., (std::is_same_v<T, Bs> ? toReturn = index : ++index));
    return toReturn;

现在我们的D 类被大大简化了,不再依赖标签调度:

struct D :  Base<B1, B2, B3>   
    template<class BX> 
    void add() 
        counters[IndexOf<BX>()].counter++;
    

    template<class BX> 
    int get()
        return counters[IndexOf<BX>()].counter;;
    
;

Live Demo


编辑:

IndexOf的C++14版本:

template<class T>
static constexpr int IndexOf()

    // find index of T in Bs...
    int toReturn = 0;
    int index = 0;
    using swallow = int[];
    (void) swallow 0, (std::is_same<T, Bs>() ? toReturn = index : ++index, 0)...;
    return toReturn;

C++14 Demo

【讨论】:

你一如既往地带着巨大的演示。 :) @javaLover 就我个人而言,当我看到一个帖子时,我通常想直接跳到可运行的代码中,所以我想其他人也会这样做。我非常高兴你的问题有这个。 注意这个问题被标记为 C++14。 @Yakk 我错过了。 OP 的链接示例使用 C++17

以上是关于非静态模板成员:可能吗?的主要内容,如果未能解决你的问题,请参考以下文章

在类模板中专门化非静态成员是不可能的吗?为啥?

C++使用模板类调用非静态成员函数

访问模板类的非模板库的静态数据

C++ 非静态成员函数的非法调用

静态成员和非静态成员的区别?

MFC中静态成员函数调用其他类的非静态变量