如何使用c++ 11风格的强类型定义创建一个新的基本类型

How can I create a new primitive type using C++11 style strong typedefs?

本文关键字:一个 类型 创建 c++ 何使用 风格 强类型 定义      更新时间:2023-10-16

我试图在c++中模仿一个独特的从Nim中输入编程语言。下面的例子不会在Nim中编译,因为编译器捕获变量ed尽管有不同的类型(Error: type mismatch: got (Euros, float))都是二进制级别的浮点数:

type
  Euros = distinct float
when isMainModule:
  var
    e = Euros(12.34)
    d = 23.3
  echo (e + d)
在c++中做到这一点的一种方法是为浮点数写一个包装器类。<>但这在导出类型的api中不能很好地工作,因为大小不能与float相同。或者即使类的大小与类的存储长度匹配Float类型,它永远不会匹配char类型的大小。如果您还实现了所有可能的操作符,如加法、减法等,则可以使用,但需要大量的输入和代码复制。

旧的问题,如创建一个新的原语类型有公认的答案使用boost的强类型定义。然而,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 %dn", dollars);
    // But the compiler catches this misuse.
    test(euros, a);
}

但这几乎是它,test()调用不会工作,因为签名不匹配,但语言仍然允许其他操作混淆类型。

同样的答案提到c++ 0x带来了强类型,所以我寻找了这个并发现Bjarne Stroustrup自己给出了c++ 11风格主题在2012.在第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;
}

我试图用最新的Xcode 5.1.1在MacOSX下编译这个命令行:

$ 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想要证明的吗?

c++ 11中没有强类型。有<chrono>单位的支持,但这是完全不同的事情。没有人能就强类型定义应该有什么样的行为达成一致,所以从来没有一个关于它们的提案取得任何进展,所以它们不仅既不在c++ 11中,也不在c++ 14中,而且在这个时候,它们将被纳入任何未来的标准也没有现实的前景。

不确定这是你想要的,它是丑陋的,但它的工作:)可以将该类型包装到模板类

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) const
    {
        return value + rhs.value;
    }
    // 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, yz现在是3种不同的类型,因为模板中使用了不同的N -s。您可以使用字段typevalue访问类型和值,如x::value(将是double 1.1)。当然,如果您直接将typedef替换为struct_typedef::type,那么您将回到起点,因为您将失去strong类型。所以你的字体应该是strong_typedef而不是strong_typedef::type

有几种方法可以解决这个问题,但是因为我正在寻找一个修复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;
}

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中有很多了。

另一种不涉及typedef且编译器不强制执行但使程序员很难出错的方法是将有问题的单元编码为struct成员。

对于我的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 };
};

和使用like

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

所以在这种策略下,编译器不会阻止你混合单元,但是如果你这样做了,错误就会一直对你尖叫:

total_expenses.euros = expence1.euros + expence2.dollars;
相关文章: