19

我试图在 C++ 中模拟与Nim 编程语言不同的类型。以下示例不会在 Nim 中编译,因为编译器会捕获变量并 具有不同的类型 ( ),尽管它们都是二进制级别的浮点数:edError: type mismatch: got (Euros, float)

type
  Euros = distinct float

when isMainModule:
  var
    e = Euros(12.34)
    d = 23.3
  echo (e + d)

在 C++ 中执行此操作的一种方法是为浮点数编写一个包装类。但这不适用于导出类型的 API,因为大小与浮点数不同。或者即使一个类的大小与浮点数的存储长度相匹配,它也永远不会与 char 类型的大小相匹配。如果您还为加法、减法等操作实现所有可能的运算符,这将起作用,但需要大量输入和重复代码。

诸如创建新的原始类型之类的较旧问题 已作为使用 boost 的强 typedef 的公认答案。然而 typedef 似乎只适用于函数类型签名, typedef 不会阻止将两个浮点继承类型添加在一起并且它们的类型完全改变(嗯,因为只是一种新类型的错觉):

#include <boost/serialization/strong_typedef.hpp>
#include <stdio.h>

BOOST_STRONG_TYPEDEF(float, money);

void test(money a, float b)
{
    int t = a + b;
    printf("value is %d", t);
}

int main()
{
    money a(5.5);
    int euros(5);
    // This is not caught!
    int dollars = a + euros;
    printf("dollars %d\n", dollars);
    // But the compiler catches this misuse.
    test(euros, a);
}

但这差不多了,test()调用将不起作用,因为签名不匹配,但是该语言仍然允许其他操作随意破坏类型。

同样的答案提到 C++0x 带来了强大的 typedef,所以我寻找这个新的支持,发现Bjarne Stroustrup 本人在 2012 年发表了 C++11 风格的主题演讲。大约在第 21 分钟,他开始谈论这些新的强类型定义。如果您只下载幻灯片,第 19 页开始讨论SI 单位,稍后第 22 页和第 23 页提到如何做到这一点。但是,我无法使这些示例正常工作。这是我设法编造的拼凑而成:

template<int M, int K, int S> struct Unit { // a unit in the MKS system
    enum { m=M, kg=K, s=S };
};
template<typename Unit> // a magnitude with a unit
struct Value {
    double val; // the magnitude
    explicit Value(double d) : val(d) {} // construct a Value from a double
};

using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value< Unit<1,0,-1> >; // meters/second type
constexpr Value<Second> operator "" _s(long double d)
// a f-p literal suffixed by ‘_s’
{
return Value<Second> (d);
}
constexpr Value<Meter> operator "" _m(long double d)
// a f-p literal suffixed by ‘_m’
{
return Value<Meter> (d);
}

int main(void)
{
    Speed sp1 = 100_m / 9.8_s;
    return 42;
}

我正在尝试在 MacOSX 下使用最新的 Xcode 5.1.1 使用命令行编译它:

$ g++ unit.cpp -std=c++11
unit.cpp:13:25: error: constexpr function's return type 'Value<Second>' is not a
      literal type
constexpr Value<Second> operator "" _s(long double d)
                        ^
unit.cpp:5:8: note: 'Value<Unit<0, 0, 1> >' is not literal because it is not an
      aggregate and has no constexpr constructors other than copy or move
      constructors
struct Value {
       ^
unit.cpp:18:24: error: constexpr function's return type 'Value<Meter>' is not a
      literal type
constexpr Value<Meter> operator "" _m(long double d)
                       ^
unit.cpp:5:8: note: 'Value<Unit<1, 0, 0> >' is not literal because it is not an
      aggregate and has no constexpr constructors other than copy or move
      constructors
struct Value {
       ^
unit.cpp:26:20: error: no matching literal operator for call to 'operator "" _m'
      with argument of type 'unsigned long long' or 'const char *', and no
      matching literal operator template
    Speed sp1 = 100_m / 9.8_s;
                   ^
unit.cpp:26:28: error: no matching literal operator for call to 'operator "" _s'
      with argument of type 'long double' or 'const char *', and no matching
      literal operator template
    Speed sp1 = 100_m / 9.8_s;
                           ^
4 errors generated.

也许幻灯片中给出的例子,我错过了更多的代码?有人有一个完整的例子来说明 Bjarne 试图展示的内容吗?

4

5 回答 5

17

C++11 中没有强类型定义。有对单位的支持,<chrono>但这是完全不同的事情。没有人能就强类型定义应该有什么行为达成一致,确切地说,所以从来没有对它们提出任何建议,所以它们不仅不在 C++11 也不在 C++14 中,在这方面没有现实的前景他们进入任何未来标准的时间。

于 2014-05-18T19:43:19.243 回答
5

C++ 编译器通常期望命令行选项-std=c++11(或-std=c++0x稍旧的选项)来激活 C++11 支持。

根本不支持 C++11 风格。

不,它完美地做到了。可以在此处查看GCC 4.7.2 的支持。要激活一些实验性功能,请通过-std=gnu++11.

Clang 3.4 实际上支持 C++11 中的几乎所有内容,并且已经支持 C++1y 中的很多内容。

于 2014-05-18T19:41:18.660 回答
5

不确定这是你想要的,它很丑,但它有效:) 你可以将类型包装到模板类中,

template <typename T, int N> // N is used for tagging
struct strong_typedef
{
    using strong_type = strong_typedef<T,N>; // typedef for the strong type
    using type = T; // the wrapped type
    T value; // the  wrapped value

    strong_typedef(T val): value(val){}; // constructor
    strong_typedef(){value={};}; // default, zero-initialization

    // operator overloading, basic example: 
    strong_type& operator+(const strong_type& rhs)
    {
        value+=rhs.value; 
        return *this;
    }

    // display it
    friend ostream& operator<<(ostream & lhs, const strong_typedef& rhs)
    {
        lhs << rhs.value;
        return lhs;
    }
};

然后将其用作

// these are all different types
strong_typedef<double, 0> x = 1.1; 
strong_typedef<double, 1> y = 2.2;
strong_typedef<double, 2> z = 3.3;

std::cout << x + x << std::endl; // outputs 2.2, can add x and x
// cout << x + y << endl; // compile-time ERROR, different types

x,y并且z现在是 3 种不同的类型,因为N模板中使用了不同的 -s。您可以使用字段type和访问类型和值value,例如x::value(将是双 1.1)。当然,如果您直接typedef使用struct_typedef::type,您将回到第一方,因为您正在丢失strong类型。所以基本上你的类型应该是strong_typedef而不是strong_typedef::type

于 2014-05-18T20:48:40.293 回答
4

有几种方法可以解决这个问题,但由于我在演示幻灯片中寻找 Bjarne 代码的修复方法,我接受了@robson3.14 在问题评论中留下的这个答案:

#include <iostream>

template<int M, int K, int S> struct Unit { // a unit in the MKS system
    enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit
struct Value {
    double val; // the magnitude
    // construct a Value from a double
    constexpr explicit Value(double d) : val(d) {} 
};

using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value<Unit<1,0,-1>>; // meters/second type

// a f-p literal suffixed by ‘_s’
constexpr Value<Second> operator "" _s(long double d)
{
    return Value<Second> (d);
}
// a f-p literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(long double d)
{
    return Value<Meter> (d);
}
// an integral literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(unsigned long long d)
{
    return Value<Meter> (d);
}

template<int m1, int k1, int s1, int m2, int k2, int s2>
Value<Unit<m1 - m2, k1 - k2, s1 - s2>> operator / (Value<Unit<m1, k1, s1>> a, Value<Unit<m2, k2, s2>> b)
{
    return Value<Unit<m1 - m2, k1 - k2, s1 - s2>>(a.val / b.val);
}

int main()
{
    Speed sp1 = 100_m / 9.8_s;
    std::cout << sp1.val;
}
于 2014-05-23T16:51:46.563 回答
0

一种不涉及 typedef 并且编译器没有强制执行的替代方案,但使程序员很难出错,它将有问题的单元编码为结构成员。

对于我的 arduino 项目,我有类似的类型

template <typename T>
struct millisecond {
    T millisecond;
    static constexpr const struct millisecond<T> zero = { 0 };
};

template <typename T>
struct microsecond {
    T microsecond;
    static constexpr const struct microsecond<T> zero = { 0 };
};

并使用

auto time_diff = millisecond<unsigned long>::zero;
time_diff.millisecond = nowMilliseconds() - s_lastPollTime.millisecond;

因此,使用这种策略,编译器不会阻止您混合单元,但如果您这样做,那么错误将始终对您尖叫

total_expenses.euros = expence1.euros + expence2.dollars;
于 2019-01-31T23:04:41.353 回答