c++:有界数的异构模板
我想创建一个可以对其施加限制的数字(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
也许是一个带有虚拟
toString
和fromString
函数的公共基类?那么你的 for 循环就变成了:Maybe a common base class with virtual
toString
andfromString
functions? Then your for loop becomes:将普通类型包装在 Boost.Operators 中怎么样?
How about wrapping your plain type in Boost.Operators?
我认为您必须将所有内容都放入一种类型(意味着忘记 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.
首先我会说,您可以在基类中提供一些
toInt
和toDouble
和toInt64
方法。然后,如果您需要双精度值,您只需提出要求即可。它需要每个人付出最少的努力。如果做不到这一点,您可以将常规的
virtual int value() const
方法替换为virtual void value(Operator&) const
方法。Operator
将提供自己的虚拟函数,每个函数都接受您想要操作的类型之一。本质上:当您在基本类型上调用
value
时,虚拟调度将确保调用正确的实现。在该实现中,您为act
提供正确的静态类型,然后重载决策就会启动。您实际上可以在Operator
实例中执行计算,或者仅存储结果并进行计算。提供一个访问器。在前一种情况下,您可以为每种类型做一些独特的事情,例如使用一种对整数类型运行速度更快但没有精度的算法。在后一种情况下,您可以更进一步,在Base
中提供一个模板化访问器。当然,最终结果是采用一种极其复杂的方式来执行我最初建议的操作。
/////////////
我刚刚注意到您对原始问题的编辑。由于有如此多的可能的基础类型,这变得不太可行。您必须在 Operator 类中提供每个基本类型的重载。您可以为基本 Operator 类提供默认实现;就像为所有整型调用
act(int)
和为所有浮点类型调用act(double)
一样,那么您将回到每个实现仅需要两个重载,加上该特定用例所需的任何其他内容。但现在我把目光投向了亚格尼。您有一个复杂的基类,只是为了提供通过不存储完整
int
来节省一些字节的设置?你真的不能将所有内容都存储为double
吗?它精确地存储了一大堆整数值。或者使用boost::variant
并将自己限制为整数、双精度数和字符串。I'll start by saying you could just provide a few
toInt
andtoDouble
andtoInt64
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 avirtual 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: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 toact
, and overload resolution kicks in. You can actually perform your calculations within theOperator
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 withinBase
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 andact(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 adouble
? That stores a whole bunch of integer values precisely. Or useboost::variant
and restrict yourself to ints, doubles, and strings.