如何防止模板化别名的隐式值保留转换

How to prevent implicit value-preserving conversion of a templated alias?

本文关键字:保留 转换 别名 何防止      更新时间:2023-10-16

我想为double的不同含义添加编译时检查。在现实世界中,我试图确保所有的计算都是以一致的单位进行的。为了这个问题的目的,我制作了一个玩具示例,其中数字有味道。

我一直在尝试基于模板参数来实现这一点。使用另一个答案中描述的c++0x别名特性,我将Number<Flavor>声明为:

enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;

这使我能够将局部变量或参数声明为Number的特定风格,然后在大多数上下文中将这些变量用作普通的Double。

我的问题是,我找不到一种方法来声明一个只接受特定风格作为参数的函数:

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

我的目标是使printCherryNumber(b)无法编译,因为bNumber<Plum>而不是Number<Cherry>。许多现有的问题解决了这个问题的变体,解决方案似乎不适用于我在Number中使用的类型别名构造。

我试过的东西

从这个答案中,我看到了添加该函数的模板化版本的建议,该版本显式地不执行任何操作或中断,如

template <typename T> void printCherryNumber(T num) = delete;

这根本没有效果,为什么要这样做?Number<Plum>实际上是double,而Number<Cherry>也是double,所以编译器从不为模板化版本而烦恼。

另一个答案建议使用单个模板化函数和静态断言,如:

template <Flavor F> void printPlumNumber(Number<F> num) {
    static_assert(F == Plum, "Wrong number flavor!");
    cout << num << endl;
}

这失败了,因为无论F的实际值如何,Number<F>仍然只是double,因此我得到了一个无法推断F的值的错误。

在其他地方,有人建议明确的专业化,但在这种情况下也失败了:

template <Flavor F> void printRaspberryNumber(Number<F> num) = delete;
template <> void printRaspberryNumber<Raspberry>(Number<Raspberry> num) {
    cout << num << endl;
}

在这里,编译器将调用视为不明确的,部分原因是它无法推断F的值。

房间里的大象

当然,我可以使Number成为形式的单值结构

template <Flavor> struct Number { double value; };

但我尽量避免这个选项,因为我对在代码中到处都有.value的想法并不感到非常兴奋,也不特别渴望为Number定义只代理到两倍的运算符。

强制性表意文字

http://ideone.com/4HiYtI

这种方法的问题:

enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;

别名模板是透明的。Number<Cherry>Number<Plum>double都是相同的类型。那根本不能解决你的问题。


您想要的通常被称为不透明typedef。你真的想要你的最后一个选择:

template <Flavor>
struct Number {
    double value;
    operator double() const { return value; } // for convenience
    Number& operator=(double ); // if necessary
    // possibly more operations
};

这样,Number<Cherry>Number<Plum>不同的类型。它们不能相互转换。并且CCD_ 25不能隐式地转换为任何一个。


您还可以看看BOOST_STRONG_TYPEDEF及其实现,它也旨在解决这个问题。

您试图避免的选项实际上是实现这一点的唯一方法。

模板别名就是它本身:别名。模板别名等效于基础类型。在各个方面。

template <Flavor> using Number = double;

这意味着CCD_ 27是CCD_。这不是别的。Number<Plum>也是double。这与对double进行全局搜索/替换几乎相同。最终结果将完全相同。类型完全相同。

您只能"声明一个只接受"特定类型的函数。除了使用模板别名外,模板别名相同的类型,因此不能声明接受double但不接受double的函数。这是一个逻辑错误。

double封装在struct中是实现这种严格类型检查的唯一方法。没那么糟糕。加入几个重载、几个operator和封装的struct将强制执行严格的类型检查,编译器可能会生成相同的代码,而不会在运行时受到惩罚。