简体   繁体   中英

non-static template member : possible?

Is it possible to create non-static template field in a class?
If no, how to workaround?

Such fields should be created at compile time as needed.

Example

I have a lot of B -class, like B1 , B2 , B3 .
(In real case, they have more meaningful names.)

I want to create a class D that has non-static template function add<BX>() that have to counter++ every time I call it, for each individual BX , for a certain instance of D.
(In real case, it does somethings more complex.)

Here is a working demo to achieve it.
Sadly, I currently have to hard-code every BX , one-by-one ( B1 , B2 , B3 ) inside D :-

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

Here is the usage. Notice that each instance of D keep its own 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;
}

I dream for something like :-

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; //???
    }
};

I know how to do it if countBX is static, but for non-static it seems to be impossible.

You don't need RTTI to solve this problem, nor std::map , which are very expensive (specially RTTI). Variadic template and inheritance can solve this for you:

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

Which is very close to what you actually wanted (you were in the right track, by the way).

Using a std::map std::unordered_map (suggestion from Yakk; thanks) of indexes and RTTI?

#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;
 }

Unfortunately until we get reflection into the standard, there will be no easy way to iterate over members of a class.

Solutions until then either involve implementing reflection yourself (hard and often uses macros which come with their own problems like debuggability) or wrapping up your types into another type (easier).

We can do this with a base class that has a std::array of Counters, one for each BX :

template<class... Bs>
struct Base
{
    std::array<Counter, sizeof...(Bs)> counters;
    // ... more on this later
};

Then our D class can derive from it and get the counters it needs:

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

The next thing we'll do is implement an IndexOf function in the base class that will allow us to transform a type (one of B1 B2 B3 ) into an index.

We can do this with type traits and fold expressions:

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

And now our D class is much simplified and doesn't rely on tag dispatch:

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


EDIT:

C++14 version of IndexOf :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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