【问题标题】:How to prevent implicit value-preserving conversion of a templated alias?如何防止模板化别名的隐式保值转换?
【发布时间】:2016-07-29 01:41:15
【问题描述】:

我想为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&lt;Plum&gt; 而不是Number&lt;Cherry&gt;。许多现有问题解决了这个问题的变体,解决方案似乎不适用于我用于Number 的类型别名构造。

我尝试过的东西

来自this answer,我看到了添加模板版本的函数的建议,该版本明确不执行任何操作或中断,如

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

这根本没有影响,为什么要这样做? Number&lt;Plum&gt; 真的是 doubleNumber&lt;Cherry&gt; 也是 double 所以编译器从不打扰模板版本。

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

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

这失败了,因为不管F 的实际值如何,Number&lt;F&gt; 仍然只是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 的值。

房间里的大象

当然,我可以将Number 设为

形式的单值结构
template <Flavor> struct Number { double value; };

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

强制性ideone

http://ideone.com/4HiYtI

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    这种方法的问题:

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

    别名模板是透明的。 Number&lt;Cherry&gt;Number&lt;Plum&gt;double 都是同一类型。这根本不能解决你的问题。


    您想要的通常称为不透明类型定义。你真的想要你的最后一个选择:

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

    这样,Number&lt;Cherry&gt;Number&lt;Plum&gt;不同的类型。它们不可相互转换。并且double 也不能隐式转换为两者。


    你也可以看看BOOST_STRONG_TYPEDEF 及其实现,它也是为了解决这个问题。

    【讨论】:

      【解决方案2】:

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

      模板别名就是这样:别名。模板别名等价于底层类型。各方面。

      template <Flavor> using Number = double;
      

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

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

      double 包装在struct 中是实现此类严格类型检查的唯一方法。它没有那么坏。折腾几个重载,几个operators,你包装的struct将强制执行严格的类型检查,编译器很可能产生相同的代码,没有运行时损失。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多