【问题标题】: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 是静态的,我知道该怎么做,但对于非静态来说,这似乎是不可能的。

【问题讨论】:

    标签: c++ templates c++14 non-static template-variables


    【解决方案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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 2011-08-15
    相关资源
    最近更新 更多