нестатический член шаблона: возможно?

Можно ли создать нестатическое поле шаблона в классе?
Если нет, как обойтись?

Такие поля следует создавать во время компиляции по мере необходимости.

Пример

У меня много B-класса, например _2 _, _ 3 _, _ 4_.
(В действительности у них более значимые имена.)

Я хочу создать класс D, который имеет нестатическую функцию шаблона add<BX>(), которая должна counter++ каждый раз, когда я ее вызываю, для каждого отдельного BX, для определенного экземпляра D.
(В действительности он делает что-то более сложное.)

Вот рабочая демонстрация для достижения этой цели.
К сожалению, в настоящее время мне приходится жестко кодировать каждый BX, один за другим (_10 _, _ 11 _, _ 12_) внутри 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;}
};

Вот использование. Обратите внимание, что каждый экземпляр 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 статичен, но для нестатических это кажется невозможным.


person javaLover    schedule 17.05.2017    source источник


Ответы (3)


Использование std::map std::unordered_map (предложение Yakk; спасибо) индексов и 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;
 }
person max66    schedule 17.05.2017
comment
Вам небезразличен порядок этой карты? - person Yakk - Adam Nevraumont; 17.05.2017
comment
Якк - нет, наверное, нет. Вы имеете в виду, что лучше std::unordered_map? - person max66; 17.05.2017
comment
Великолепное решение RTTI! - person AndyG; 17.05.2017
comment
@javaLover Он делает разные вещи, чем OP: код OP обрабатывает фиксированный набор типов; это обрабатывает неограниченный набор типов. Также обратите внимание, что операция тривиальна (++), в то время как в реальном случае операция будет дороже (поэтому соотношение снизится). И max66, да, map это действительно ordered_map: если вам не нужен порядок, подумайте unordered_map. - person Yakk - Adam Nevraumont; 17.05.2017
comment
@Yakk - спасибо; Я люблю C ++ 11 и C ++ 14, но моя голова запечатлена на C ++ 98. - person max66; 18.05.2017
comment
@javaLover - для медлительности попробуйте с std::unordered_map, как предлагает Yakk; может помочь; но взгляните также на ответ Леонардо: мне кажется, что вы хотели (за исключением того, что вам нужно явно указать список типов BX). - person max66; 18.05.2017
comment
@AndyG - спасибо; но взгляните на решение Леонардо. Просто и элегантно (ИМХО). - person max66; 18.05.2017
comment
Я проверил и обнаружил, что std::unordered_map не помогает. Мне все еще нравится ваше решение из-за высокой ремонтопригодности, которая является основной проблемой в этом вопросе. - person javaLover; 18.05.2017

Вам не нужно ни 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;
    }
};

Что очень близко к тому, что вы действительно хотели (кстати, вы были на правильном пути).

person Leonardo    schedule 17.05.2017
comment
Это было так просто ... очень красивое и элегантное решение. - person max66; 18.05.2017
comment
Просто примечание для себя: в этом решении я должен вручную поместить все BX в D, например. D<B1,B2,B3> d1; похоже на решение AndyG. - person javaLover; 18.05.2017
comment
@javaLover, к сожалению, да. - person Leonardo; 18.05.2017

К сожалению, пока мы не получим отражение в стандарте, не будет простого способа перебирать члены класса.

До тех пор решения либо включают реализацию отражения самостоятельно (жестко и часто используют макросы, которые имеют свои собственные проблемы, такие как возможность отладки), либо перенос ваших типов в другой тип (проще).

Мы можем сделать это с помощью базового класса, который имеет 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 в базовом классе, которая позволит нам преобразовать тип (один из B1 B2 B3) в индекс.

Мы можем сделать это с помощью признаков типа и выражений свертки:

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

person AndyG    schedule 17.05.2017
comment
Вы, как всегда, приходите с огромной демонстрацией. :) - person javaLover; 17.05.2017
comment
@javaLover лично я обычно хочу сразу перейти к работающему коду, когда вижу сообщение, поэтому я предполагаю, что другие тоже. Мне очень понравилось, что это был ваш вопрос. - person AndyG; 17.05.2017
comment
Обратите внимание, что этот вопрос помечен как C ++ 14. - person Yakk - Adam Nevraumont; 17.05.2017
comment
@Yakk Я это пропустил. Связанный пример OP был с С ++ 17 - person AndyG; 17.05.2017