关于 C++ 中的声明式编程
我经常面临将一个 API 的参数空间映射到另一个 API 的参数空间的问题。我经常看到这个问题是通过嵌套嵌套... switch 语句来解决的。
我想知道是否会有一个库或一种技术允许您“声明”映射而不是“编程”它。
一个简单的示例包括将两个枚举的值合并为一个:
namespace sourceAPI {
struct A { typedef e { A1, A2, A3 } };
struct B { typedef e { B1, B2 } };
}
namespace targetAPI {
struct AB { typedef e { A1B1, A1B2, A2B1, A2B2, A3B1, A3B2 } };
}
其中映射通常是这样完成的
switch( a ){
case( A::A1 ): switch( b ) {
case( B::B1 ): return A1B1;
case( B::B2 ): return A1B2;
....
}
并且此映射仍然需要一个“反向”开关。
但我宁愿喜欢一些“密集”的东西,比如
declare( source( A::A1, B::B1 ), target( AB::A1B1 ) );
declare( source( A::A1, B::B2 ), target( AB::A1B2 ) );
....
有人见过这样的技术、框架或库吗?
Often I face the problem of mapping the parameter space of one API onto the parameter space of another one. Often I see this solved by nested nested nested ... switch statements.
And I was wondering if there would happen to be a library or a technique that allows you to 'declare' the mapping instead of 'program' it.
A trivial example would consist of merging the values of two enumerates into one:
namespace sourceAPI {
struct A { typedef e { A1, A2, A3 } };
struct B { typedef e { B1, B2 } };
}
namespace targetAPI {
struct AB { typedef e { A1B1, A1B2, A2B1, A2B2, A3B1, A3B2 } };
}
In which the mapping is often done like
switch( a ){
case( A::A1 ): switch( b ) {
case( B::B1 ): return A1B1;
case( B::B2 ): return A1B2;
....
}
And this mapping still needs a 'reverse' switch, too.
But I would rather like something 'dense' like
declare( source( A::A1, B::B1 ), target( AB::A1B1 ) );
declare( source( A::A1, B::B2 ), target( AB::A1B2 ) );
....
Has anyone seen such a technique or framework or library?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您可以使用 Boost.Bimap ,它提供两种类型之间的双向映射。
它有一点运行时开销(通常,与为此目的使用一对 std::map 所获得的开销大致相同,但这并不是很多)。
不过,它确实允许您定义与示例一样密集的映射;通常,您只需将对添加到地图中,一次一对。
You can use Boost.Bimap, which provides a bidirectional mapping between two types.
It has a bit of runtime overhead (generally, roughly the same amount of overhead you would get by using a pair of
std::map
s for this purpose, which isn't a whole lot).It does allow you to define mappings about as densely as your example, though; generally you just add pairs to the map, one pair at a time.
使用表驱动的方法很好 - 它是等效的。
您需要担心两个问题:枚举布局更改(更改序数)或枚举内容更改(添加/删除)。
无论您选择什么解决方案,您都希望减轻这两个问题引起的问题。
在我自己的工作中,我更喜欢使用这样的模式:
在事物可能嵌套的地方,我调用另一个 ToXXXX 方法。
对于枚举,我的 ToXXXX 方法要么是单个开关,要么是表查找(或者在某些情况下是表达式转换),并且我使用抛出的代码检查输入范围(无论是边界检查还是开关中的默认语句)。
对我来说,从一种类型转换为另一种类型的机制并不重要,重要的是通过硬失败和快速失败来防止 API 更改时出现错误的工程。这样想一下:您会浪费更多时间输入完整的 switch 语句(嵌套或非嵌套)并进行错误检查或从超出范围且未检查的枚举中跟踪错误吗?
Using a table driven approach is fine - it's equivalent.
There are two issues that you need to worry about: enum layout changes (which change ordinality) or enum content changes (addition/removal).
Whatever solution you choose, you want to mitigate the problems caused by these two issues.
In my own work, I prefer to use a pattern like this:
Where things might nest, I call out to another ToXXXX method.
For enums, my ToXXXX methods are either a single switch or a table lookup (or in some cases an expression transform), and I check input ranges with code that throws (whether it's bounds checking or a default statement in a switch).
To me, the mechanism to translate from one type to another is less important than the engineering that prevents bugs from occurring when API's change by failing hard and failing fast. Think about it this way: would you lose more time typing in a complete switch statement (nested or not) with error checking or tracking down a bug from an enum that is out of range and wasn't checked?
在许多情况下,您可以通过简单的查找表来完成此任务。由于枚举类型可以转换为整数值,因此您可以将它们用作不同类型枚举类型数组的索引,从而实现快速轻松的转换。它有一个令人愉快的副作用,那就是它的制造速度与人类所能达到的速度差不多。根据您的使用情况,查找表可能会变得相当大(但是,如果您为每个枚举创建一个带有一种情况的 switch 语句,那么查找表会变得更大)。另外,如果您需要双向转换,那么您必须创建 2 个查找表,每个方向一个。
另外,请注意,许多编译器可以将枚举类型优化为存储每个值所需的最小数据类型。有一些方法可以解决这个问题(通常是编译器标志,或者您可以声明一个“虚拟”值,例如 0xffffffff 来强制进行 32 位枚举),但值得注意。
如果您有非整数值,则可以使用映射。 STL 包括多个变体,正如其他人提到的,boost 有一个很好的双向变体。
In many cases you can accomplish this with simple lookup tables. Since enumerated types can be cast to integer values, you can use them as the index into an array of enumerated types of a different kind, hence doing quick and easy conversion. It has the pleasant side effect of being about as fast as it's humanly possible to make this sort of thing. Depending upon your use, lookup tables can get rather large (but then, if you're making a switch statement with one case for each enumeration, that would be even larger). Also, if you need bi-directional conversion then you'd have to make 2 lookup tables, one for each direction.
Also, be aware that many compilers can optimize enumerated types down to the minimum data type needed to store every value. There are ways around this (often a compiler flag, or you can just declare a "dummy" value of something like 0xffffffff to force a 32-bit enumeration), but it's worth noting.
If you have non-integer values, you can use maps. The STL includes several varieties, and as somebody else mentioned, boost has a nice one that's bidirectional.
对于这种特定类型的任务,您通常可以稍微作弊,只需对两个枚举使用不重叠的位模式即可。例如:
在这种特殊情况下,由于 A 恰好具有三个成员,因此结果甚至是连续的值范围。当成员数量(除了一个枚举之外的所有成员)不小于 2 的幂次方时,结果将是不连续的。
我很确定你的问题确实是更普遍的,而不仅仅是找到一种方法来处理这个特定的问题。事实上,我怀疑这个例子纯粹是假设的。不幸的是,很难猜测您可能关心哪些其他类型的问题。 C++ 中声明式编程的示例确实不少。举几个明显的例子,几乎所有使用 Boost Spirit 或 Boost Xpressive 的东西最终都会至少进行一些声明性编程。然而,无论好坏,这两者都致力于解决类似的问题,而这些问题恰好与您似乎关心的问题有很大不同。
For this particular type of task, you can often cheat a bit, and simply use non-overlapping bit patterns for the two enumerations. For example:
In this particular case, since A has exactly three members, the result is even a contiguous range of values. When the number of members (of all but one enumeration) is anything but one fewer than a power of two, the result will be non-contiguous though.
I'm pretty sure your question is really intended to be more general than just finding a way to deal with this particular problem though. In fact, I suspect the example is purely hypothetical. Unfortunately, it's hard to guess at what other types of problems you might care about. There are certainly quite a few examples of declarative programming in C++. For a couple of obvious examples, virtually everything that uses Boost Spirit or Boost Xpressive ends up doing at least some declarative programming. For better or worse, however, both of these are devoted to similar problems that happen to be quite a bit different from the ones you seem to care about.
以 boost::tuple 作为键的 std::map。
如果您不介意在声明中使用“make_tuple”,那么您的声明中已经免费拥有了可变键元素。您需要“make_tuple”来进行实际的转换。
编辑:
当需要范围或通配符时,事情确实会变得更加复杂
A std::map with a boost::tuple as key.
If you don't mind using "make_tuple" in your declaration, then you already have variable key elements in your declaration for free. You need "make_tuple" to do the actual conversion.
Edit:
Things do become more complicated when ranges or wildcards are needed