c ++: гетерогенный шаблон для ограниченных чисел - PullRequest
2 голосов
/ 25 апреля 2011

Я хочу создать список (std :: container или даже list * будет в порядке) чисел (int и double), для которых могут быть наложены ограничения.

template<typename T> 
class setting {
  public:
    std::string name, units;
    T value, min, max, step;

    setting(std::string n, T val) : name(n), value(val) { }

    setting operator++(int) {
      setting tmp = *this;
      value += step; if(value > max) value = max;
      return tmp;
    }
};
...
list.add(new setting<int>("channel", 4));
list.add(new setting<double>("amplitude", 5.6));
...
for(int i = 0; i < list.size(); i++)
  std::cout << list[i].name << ": " << list[i].value << std::endl;

Я пробовал это несколькими способами, но я не доволен ни одним из них.Не может быть получено из общей базы, потому что базовый тип не знает о `значении ', потому что он не знает тип или должен иметь тип, определенный заранее.Попробовал это с шаблонами макросов, но унылый чувствует себя неряшливо.Есть ли способ сделать это, не прибегая к объединению всех типов с идентификатором типа, чтобы выбрать правильный член?

boost :: многоадресный вариант с перегруженными конструкторами, кажется, делает то, что я пока хочу, за исключением того, чтоперечислить все типы во время компиляции:

class setting {
   public:
      boost::variant<
         bool,
         int8_t,
         uint8_t,
         int16_t,
         uint16_t,
         int32_t,
         uint32_t,
         int64_t,
         uint64_t,
         float,
         double,
         std::string
            > value;

      std::string name;

      setting(std::string n, int v) : name(n), value(v) { }
      setting(std::string n, double v) : name(n), value(v) { }
      setting(std::string n, std::string v) : name(n), value(v) { }
};

typedef std::map<std::string, setting*> MapType;
typedef MapType::const_iterator MapItr;

int main() {
   MapType settinglist;

   settinglist["height"] = new setting("height", 1.3);
   settinglist["width"]  = new setting("width", 5);
   settinglist["name"]   = new setting("name", "the name");

   for(MapItr i = settinglist.begin(); i != settinglist.end(); ++i) {
      std::cout << i->second->name
         << " : " << i->second->value
         << std::endl;
   }

   return 0;
};

дает:

height : 1.3
name : the name
width : 5

Ответы [ 4 ]

0 голосов
/ 26 апреля 2011

Я начну с того, что вы можете просто предоставить несколько методов toInt и toDouble и toInt64 в базовом классе. Затем, если вам нужно двойное значение, вы просто попросите его. Это требует минимальных усилий со стороны каждого.

В противном случае вы можете заменить обычный virtual int value() const метод на virtual void value(Operator&) const. Operator будет предоставлять свои собственные виртуальные функции, каждый из которых принимает один из типов, с которыми вы хотели бы работать. По существу:

struct Operator {
    virtual act(int) = 0;
    virtual act(double) = 0;
    //repeat for other types
};

struct Base {
    virtual value(Operator&) const = 0;
};

Когда вы вызываете value для вашего базового типа, виртуальная диспетчеризация обеспечит вызов правильной реализации. В рамках этой реализации вы предоставляете правильный статический тип для act и включается разрешение перегрузки. Вы можете фактически выполнить свои вычисления в экземпляре Operator или просто сохранить результат и предоставить средство доступа. В первом случае вы можете сделать что-то уникальное для каждого типа, например, использовать алгоритм, который работает быстрее с целочисленными типами, но без точности. В последнем случае вы можете пойти еще дальше и предоставить шаблонный метод доступа в Base

struct Base {
    //other stuff
    template<typename Operator>
    typename Operator::ResultType value() const {
        Operator op;
        value(op); 
        return op.result();
    }
}
// later
cout << basePtr->get<IntGetter>();

Конечно, конечный результат этого - чрезвычайно запутанный способ сделать то, что я первоначально предложил.

/////// Я только что заметил ваше изменение к исходному вопросу. При таком количестве возможных базовых типов это становится гораздо менее осуществимым. Вы должны обеспечить перегрузки каждого примитивного типа в ваших классах Operator. Вы можете предоставить реализации по умолчанию для базового класса Operator; как при вызове act(int) для всех целочисленных типов и act(double) для всех типов с плавающей запятой, вы вернетесь к двум обязательным перегрузкам на реализацию, плюс любые дополнительные, которые вам нужны для этого конкретного варианта использования.

Но теперь я смотрю в сторону ЯГНИ. У вас есть сложный базовый класс, просто вы можете предоставить настройки, которые экономят несколько байтов, не сохраняя полный int? Разве вы не можете просто хранить все как double? Это хранит целую кучу целочисленных значений точно. Или используйте boost::variant и ограничьте себя строчками, двойниками и строками.

0 голосов
/ 26 апреля 2011

Как насчет упаковки вашего простого типа в Boost.Operators ?

template <class T, T max = numeric_limits<T>::max(), T min = 0>
class Saturate
    : boost::operators<Saturate<T, max, min>, T >
{
private:
    T _value;
    void normalize() {
        if(_value < min) _value = min;
        if(_value > max) _value = max;
    }
    void toNormal(T t) {
        if(t < min) return min;
        if(t > max) return max;
        return t;
    }
public:
    Saturate(T t = T()) : _value(toNormal(t)) {}
    T value() { return _value; }
    Saturate& operator+=(const Saturate& x)
      { _value += x._value; normalize(); return *this; }
    Saturate& operator-=(const Saturate& x)
      { _value -= x._value; normalize(); return *this; }
    ...
};
...
std::vector<Saturate<int, 1023, -1023> > volume;
...
volume[3] = 50000; // really loud
std::cout << volume[3].value(); // Not so loud
0 голосов
/ 26 апреля 2011

Я думаю, вам придется либо вписать все в один тип (то есть забыть о int, просто использовать double), либо определить более общий базовый тип.

Проблема, которую вы заметили с базовым базовым типом, заключается в том, что он, в общем, универсальный. Вы теряете информацию о типе, которую не сможете получить позже при доступе к элементам в контейнере.

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

0 голосов
/ 25 апреля 2011

Может быть, общий базовый класс с виртуальными toString и fromString функциями?Тогда ваш цикл for становится:

list<setting_base> lst;
for( list<setting_base>::iterator it = lst.begin(); it != lst.end(); ++it )
     std::cout << it->name << ": " << it->toString() << std::endl;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...