c++:有界数字的异构模板

c++: heterogeneous template for bounded numbers

本文关键字:异构 数字 c++      更新时间:2023-10-16

我想创建一个列表(std::container或甚至列表*都可以)的数字(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;

我尝试了几种不同的方法,但我对它们都不满意。不能从公共基类派生,因为基类类型不知道' value',因为它不知道类型,或者必须提前定义类型。用宏模板试过,但是downcast感觉很草率。有没有一种方法可以做到这一点,而不需要使用带有类型标识符的所有类型的联合来选择正确的成员?

到目前为止,带重载构造函数的

boost::variant似乎满足了我的要求,除了必须在编译时枚举所有类型:

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

可能是虚拟toStringfromString函数的公共基类?然后你的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;

如何将普通类型包装在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

我认为你要么把所有东西都塞进一个类型(意思是忘记整型,只使用double),要么定义一个更通用的基类型。

您注意到的泛型基类型的问题是,它是泛型的。您将丢失类型信息,这些信息在以后访问容器中的元素时无法恢复。

我认为你的设计目标不一致。您应该创建一个足以满足您需求的通用接口(这是类型无关的),或者选择一种类型并坚持使用它。

我将首先说您可以在基类中提供一些toInttoDoubletoInt64方法。然后,如果你需要双精度值,你只需要请求它。它只需要每个人付出最少的努力。

如果做不到这一点,您可以用virtual void value(Operator&) const方法替换常规的virtual int value() 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),那么每个实现只需要两个重载,再加上特定用例所需的任何额外重载。

但是现在我正在寻找YAGNI。你有一个复杂的基类,只是这样你可以提供设置,节省几个字节不存储完整的int ?你真的可以不把所有东西都存储为double吗?它精确地存储了一堆整数值。或者使用boost::variant,并将自己限制为int型、双精度型和字符串。