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

时间:2016-07-29 01:41:16

标签: c++ templates

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

我一直在尝试基于模板参数实现此目的。使用another answer中描述的c ++ 0x别名功能,我将Number<Flavor>声明为:

enum Flavor { Cherry, Plum, Raspberry };

template <Flavor> using Number = double;

这使我能够将局部变量或参数声明为Number的特定风格,然后在大多数情况下将这些变量用作普通双精度。

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

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的类型别名构造无效。

我试过的东西

this answer开始,我看到了添加模板化版本的建议,该模板显式无效或中断,如

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

这根本没有效果,为什么要这样呢? Number<Plum>实际上是doubleNumber<Cherry>也是double所以编译器永远不会对模板化版本产生麻烦。

Another answer建议使用单个模板化函数和静态断言,如:

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

此操作失败,因为无论F的实际值如何,Number<F>仍然只是double,因此我收到的错误是无法推断F的值}。

其他人suggests explicit specialization,在这种情况下也失败了:

template <Flavor F> void printRaspberryNumber(Number<F> num) = delete;

template <> void printRaspberryNumber<Raspberry>(Number<Raspberry> num) {
    cout << num << endl;
}

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

房间里的大象

当然,我可以以{/ p>的形式使Number为单值结构

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

但是我试图避免这个选项,因为我对代码中到处都有.value的想法并不十分激动,我也不是特别渴望为Number定义运算符只需代理下来加倍。

强制性意识

http://ideone.com/4HiYtI

2 个答案:

答案 0 :(得分:2)

这种方法的问题:

enum Flavor { Cherry, Plum, Raspberry };

template <Flavor> using Number = double;

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

您想要的通常称为 opaque 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> 不同类型。他们可以相互转换。并且double不能隐式转换为其中任何一个。

您还可以查看BOOST_STRONG_TYPEDEF及其实施情况,它也可以解决此问题。

答案 1 :(得分:1)

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

模板别名就是它的一个别名。模板别名等同于基础类型。在所有方面。

template <Flavor> using Number = double;

这意味着Number<Flavor>double。这不是别的什么。 Number<Plum>也是double。这与将其中任何一个全局搜索/替换为double几乎相同。最终结果将是相同的。类型完全相同。

您只能“声明一个只接受”特定类型的函数。除了使用模板别名外,模板别名 的类型相同,因此无法声明接受double的函数,但不能接受double。这是合乎逻辑的虚假。

double包裹在struct中是实现此类严格类型检查的唯一方法。没那么糟糕。抛弃一些重载,几个operator,并且你的包裹struct将强制执行严格的类型检查,编译器可能会产生相同的代码,没有运行时惩罚。