[英]How to prevent implicit value-preserving conversion of a templated alias?
I'd like to add compile-time checking for different meanings of double
. 我想为double
不同含义添加编译时检查。 In the real world, I'm trying to make sure all calculations are done in consistent units. 在现实世界中,我试图确保所有计算均以一致的单位进行。 For the purposes of this question, I've concocted a toy example in which numbers have flavors. 出于这个问题的目的,我构想了一个玩具示例,其中数字具有味道。
I've been trying to achieve this based on a template parameter. 我一直在尝试根据模板参数实现这一目标。 Using the c++0x aliases feature described in another answer , I declared a Number<Flavor>
as: 使用另一个答案中描述的c ++ 0x别名功能,我将Number<Flavor>
声明为:
enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;
This gives me the ability to declare local variables or parameters as particular flavors of Number, then use those variables as ordinary doubles in most contexts. 这使我能够将局部变量或参数声明为Number的特定样式,然后在大多数情况下将这些变量用作普通的double。
My problem is, I can't find a way to declare a function that will only accept a particular flavor as its argument: 我的问题是,我找不到一种方法来声明仅接受特定形式作为其参数的函数:
void printCherryNumber(Number<Cherry> num) { cout << num << endl; }
int main() {
Number<Cherry> a(5);
Number<Plum> b(6);
Number<Raspberry> c(3.1415);
printCherryNumber(a);
printCherryNumber(b); // O, if only this could be a compiler error.
return 0;
}
My goal is to make printCherryNumber(b)
fail to compile because b
is a Number<Plum>
not Number<Cherry>
. 我的目标是使printCherryNumber(b)
无法编译,因为b
是Number<Plum>
而不是Number<Cherry>
。 Many existing questions tackle variations on this problem with solutions that seem to not work on the type alias construct I've used for Number
. 许多现有的问题使用解决方案来解决此问题,这些解决方案似乎不适用于我用于Number
的类型别名构造。
From this answer , I see the suggestion to add a templated version of the function that explicitly does nothing or breaks, as in 从这个答案中 ,我看到建议添加一个函数的模板化版本,该版本显式不执行任何操作或中断操作,如
template <typename T> void printCherryNumber(T num) = delete;
This has no effect at all, and why should it? 这根本没有效果,为什么呢? Number<Plum>
is really double
and Number<Cherry>
is also double
so the compiler never bothers with the templated version. Number<Plum>
确实是double
而Number<Cherry>
也是double
所以编译器永远不会为模板版本烦恼。
Another answer suggests using a single templated function and static asserts, as in: 另一个答案建议使用单个模板函数和静态断言,如下所示:
template <Flavor F> void printPlumNumber(Number<F> num) {
static_assert(F == Plum, "Wrong number flavor!");
cout << num << endl;
}
This fails because regardless of the actual value of F
, Number<F>
is still just double
and so I get an error about not being able to infer the value of F
. 之所以失败,是因为无论F
的实际值如何, Number<F>
仍然只是double
,因此我收到一个无法推断F
值的错误。
Elsewhere someone suggests explicit specialization , which also fails for this case: 在其他地方,有人建议使用显式专门化 ,但在这种情况下也失败了:
template <Flavor F> void printRaspberryNumber(Number<F> num) = delete;
template <> void printRaspberryNumber<Raspberry>(Number<Raspberry> num) {
cout << num << endl;
}
Here, the compiler treats the call as ambiguous, in part again because it can't infer a value for F
. 在这里,编译器将调用视为模棱两可的,部分原因是因为它无法推断出F
的值。
I could, of course, make Number
a single-value struct in the form of 我当然可以使Number
成为以下形式的单值结构
template <Flavor> struct Number { double value; };
but I'm trying to avoid this option because I'm not terribly thrilled about the idea of having .value
all over everywhere in my code, nor am I especially eager to define operators for Number
that just proxy down to double. 但是我试图避免使用此选项,因为我对在代码中到处都具有.value
的想法并不感到非常兴奋,我也不特别希望为Number
代理定义两倍的运算符。
The problem with this approach: 这种方法的问题:
enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;
is that alias templates are transparent. 别名模板是透明的。 Number<Cherry>
, Number<Plum>
, and double
are all the same type. Number<Cherry>
, Number<Plum>
和double
都是相同的类型。 That doesn't solve your problem at all. 那根本无法解决您的问题。
What you want is typically called an opaque typedef . 您想要的通常称为opaque typedef 。 You really do want your last option: 您确实想要最后一个选择:
template <Flavor>
struct Number {
double value;
operator double() const { return value; } // for convenience
Number& operator=(double ); // if necessary
// possibly more operations
};
This way, Number<Cherry>
and Number<Plum>
are different types . 这样, Number<Cherry>
和Number<Plum>
是不同的类型 。 They are not convertible to each other. 它们不能相互转换。 And double
is not implicitly convertible to either. 并且double
不能隐式转换为任何一个。
You can also take a look at BOOST_STRONG_TYPEDEF
and its implementation, it's intended to solve this problem too. 您还可以查看BOOST_STRONG_TYPEDEF
及其实现,它也旨在解决此问题。
The option you're trying to avoid is really the only way to do this. 您试图避免的选项实际上是唯一的方法。
A template alias is what it is: an alias. 模板别名是什么:别名。 A template alias is equivalent to the underlying type. 模板别名等效于基础类型。 In all respects. 在各方面。
template <Flavor> using Number = double;
This means that Number<Flavor>
is a double
. 这意味着Number<Flavor>
是double
。 It is not anything else. 没什么 Number<Plum>
is a double
, too. Number<Plum>
也是double
。 This is pretty much the same as doing a global search/replace of either one of them to double
. 这与对其中任何一个进行double
的全局搜索/替换几乎相同。 The end result will be identical. 最终结果将是相同的。 The type is exactly the same. 类型完全相同。
You can only "declare a function that will only accept" a specific type. 您只能“声明仅接受的功能”特定类型。 Except using a template alias, the template alias is the same type, as such it is not possible to declare a function that accept a double
, but does not accept a double
. 除了使用模板别名外,模板别名是相同的类型,因此不可能声明一个接受double
的函数,但是不接受double
的函数。 It is a logical falsity. 这是一个逻辑上的虚假。
Wrapping a double
in a struct
is the only way to achieve strict type checking, of this kind. 在struct
包装一个double
是实现这种严格类型检查的唯一方法。 It's not that bad. 没那么糟糕。 Toss in a few overloads, a few operator
s, and your wrapped struct
will enforce strict type checking, and the compiler is likely to produce identical code, with no runtime penalty. 抛出一些重载,几个operator
,并且包装的struct
将强制执行严格的类型检查,并且编译器可能会生成相同的代码,而不会造成运行时损失。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.