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

发布于 2024-11-03 13:23:32 字数 1992 浏览 1 评论 0原文

我想创建一个可以对其施加限制的数字(int 和 double)列表(std::container 甚至 list* 都可以)。

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::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

I want to create a list (std::container or even list* would be ok) of numbers (int and double) that can have limits imposed on them.

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;

I've tried this a couple of different ways but I'm not happy with any of them. Can't derive from a common base because the base type doesn't know about `value' because it doesn't know the type or has to have the type defined ahead of time. Tried it with macro templates but the downcast feels sloppy. Is there a way to do this without resorting to a union of all types with a type identifier to select the right member?

boost::variant with overloaded constructors seems to do what I want so far, except for having to enumerate all the types at compile time:

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

gives:

height : 1.3
name : the name
width : 5

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夜灵血窟げ 2024-11-10 13:23:32

也许是一个带有虚拟 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;

Maybe a common base class with virtual toString and fromString functions? Then your for loop becomes:

list<setting_base> lst;
for( list<setting_base>::iterator it = lst.begin(); it != lst.end(); ++it )
     std::cout << it->name << ": " << it->toString() << std::endl;
云醉月微眠 2024-11-10 13:23:32

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

How about wrapping your plain type in 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
满天都是小星星 2024-11-10 13:23:32

我认为您必须将所有内容都放入一种类型(意味着忘记 int,只需使用 double),或者定义一个更通用的基本类型。

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

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

I think you'll have to either fit everything into one type (meaning forget about int, just use double) or, define a more generic base type.

The problem that you noted with the generic base type is that it's, well generic. You're losing type information that you can't later get back when your accessing the elements in the container.

I think that your design goal is inconsistent. You should either create a generic interface that's good enough for your needs (that's type agnostic) or pick one type and stick to it.

高跟鞋的旋律 2024-11-10 13:23:32

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

如果做不到这一点,您可以将常规的 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 并将自己限制为整数、双精度数和字符串。

I'll start by saying you could just provide a few toInt and toDouble and toInt64 methods in your base class. Then, if you need the double value, you just ask for it. It requires a minimal amount of effort on everybody's part.

Failing that, you could replace a regular virtual int value() const method with a virtual void value(Operator&) const one. Operator would provide virtual functions of its own that each accept one of the types you would want to act on. Essentially:

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

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

When you call value on your base type, virtual dispatch will ensure the correct implementation gets called. Within that implementation, you provide the proper static type to act, and overload resolution kicks in. You can actually perform your calculations within the Operator instance, or just store the result and provide an accessor. In the former case, you can do something unique for each type, like use an algorithm that works faster with integer types but without the precision. In the later case, you can go one step further and provide a templated accessor within 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>();

Of course, the end result of that is an extremely convoluted way of doing what I initially suggested.

///////
I just noticed your edit to the original question. With so many possible base types, this becomes much less feasible. You have to provide overloads of each primitive type within your Operator classes. You could provide default implementations to the base Operator class; like calling act(int) for all integral types and act(double) for all floating point types, then you'd be back down to just two required overloads per implementation, plus whatever additional ones you need for that specific use case.

But now I'm looking towards YAGNI. You've got a complicated base class, just so you can provide settings that save a few bytes by not storing a full int? Can you really not just store everything as a double? That stores a whole bunch of integer values precisely. Or use boost::variant and restrict yourself to ints, doubles, and strings.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文