C++ 11引入了用户定义的文字,这将允许基于现有文本(采用新的文本语法的int
,hex
,string
,float
),使得任何类型的将能够具有字介绍.
例子:
// imaginary numbers std::complexoperator "" _i(long double d) // cooked form { return std::complex (0, d); } auto val = 3.14_i; // val = complex (0, 3.14) // binary values int operator "" _B(const char*); // raw form int answer = 101010_B; // answer = 42 // std::string std::string operator "" _s(const char* str, size_t /*length*/) { return std::string(str); } auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer // units assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds
乍一看,这看起来非常酷,但我想知道它是多么适用,当我试图想到有后缀_AD
并_BC
创建日期时,我发现由于操作员的订单,它有问题.1974/01/06_AD
首先会评估1974/01
(作为简单的int
s),并且只是稍后06_AD
(或者说8月和9月不得不写出没有0
八进制的原因).这可以通过使语法1974-1/6_AD
使得运算符评估顺序起作用但是它很笨重来解决.
那么我的问题归结为这个,你觉得这个功能是否合理?您想要定义哪些其他文字将使您的C++代码更具可读性?
更新语法以适应2011年6月的最终草案
乍一看,它似乎是简单的语法糖.
但是在深入研究时,我们发现它不仅仅是语法糖,因为它扩展了C++用户的选项来创建用户定义的类型,其行为与不同的内置类型完全相同.在这里,这个小小的"奖励"是C++中非常有趣的C++ 11补充.
我在过去几年编写的代码中看到的用处很少,但仅仅因为我没有在C++中使用它并不意味着它对另一个C++开发人员来说并不重要.
我们在C++(和C中,我猜),编译器定义的文字中使用,将整数作为短整数或长整数,实数作为float或double(或甚至long double),字符串作为普通或宽字符.
在C++中,我们有可能创建自己的类型(即类),可能没有开销(内联等).我们有可能在它们的类型中添加运算符,使它们的行为类似于类似的内置类型,这使得C++开发人员可以像使用语言本身一样自然地使用矩阵和复数.我们甚至可以添加强制转换操作符(这通常是一个坏主意,但有时候,它只是正确的解决方案).
我们仍然错过了一件事,让用户类型表现为内置类型:用户定义的文字.
所以,我猜这是语言的自然演变,但要尽可能完整:" 如果你想创建一个类型,并且你希望它像内置类型那样行事,那么这里就是工具. .. "
我猜这与.NET决定使每个原语成为结构(包括布尔值,整数等)非常类似,并且所有结构都派生自Object.这个决定使得.NET在使用原语时远远超出Java的范围,无论Java将在其规范中添加多少装箱/拆箱黑客.
这个问题是你的回答.不是Bjarne Stroustrup.不是Herb Sutter.不是C++标准委员会的成员.这就是为什么你可以选择C++,它们不会仅仅为内置类型限制有用的表示法.
如果您需要它,那么这是一个受欢迎的补充.如果你不这样做,那么......不要使用它.这将花费你什么.
欢迎使用C++,这是一种可选功能的语言.
臃肿和复杂(双关语)之间存在差异.
就像Niels所展示的那样,用户定义的文字添加到C++有哪些新功能?,能够写一个复数是C"和C++"最近"添加的两个特性之一:
// C89: MyComplex z1 = { 1, 2 } ; // C99: You'll note I is a macro, which can lead // to very interesting situations... double complex z1 = 1 + 2*I; // C++: std::complexz1(1, 2) ; // C++11: You'll note that "i" won't ever bother // you elsewhere std::complex z1 = 1 + 2_i ;
现在,使用运算符重载,C99"双复数"类型和C++"std :: complex"类型都能够相乘,相加,相减等.
但是在C99中,他们只是添加了另一种类型作为内置类型,以及内置的运算符重载支持.并且他们添加了另一个内置的文字功能.
在C++中,他们只是使用了语言的现有特性,看到文字特征是语言的自然演变,因此添加了它.
在C中,如果你需要对另一种类型使用相同的符号增强,那么在你游说添加你的量子波函数(或3D点,或你在工作领域中使用的任何基本类型)之前,你就不幸了. C标准作为内置类型成功.
在C++ 11中,您可以自己完成:
Point p = 25_x + 13_y + 3_z ; // 3D point
它臃肿了吗?不,需要存在,如C和C++复合体如何需要一种表示其文字复杂值的方式所示.
这是错误的设计?不,它被设计为其他所有C++功能,并且具有可扩展性.
它仅用于表示法吗?不,因为它甚至可以为您的代码添加类型安全性.
例如,让我们想象一下面向CSS的代码:
css::Font::Size p0 = 12_pt ; // Ok css::Font::Size p1 = 50_percent ; // Ok css::Font::Size p2 = 15_px ; // Ok css::Font::Size p3 = 10_em ; // Ok css::Font::Size p4 = 15 ; // ERROR : Won't compile !
然后,很容易对值的赋值强制执行强类型.
好问题.这些函数可以命名空间吗?如果是,那么累积奖金!
无论如何,如果一个工具使用不当,你可以自杀.C很强大,如果误用C枪,你就可以开枪了.C++有C枪,还有手术刀,泰瑟枪,以及你在工具箱中可以找到的任何其他工具.你可以滥用手术刀并将自己流血至死.或者您可以构建非常优雅和健壮的代码.
因此,像每个C++功能一样,您真的需要它吗?在C++中使用它之前必须回答的问题.如果你不这样做,它将不会花费你.但如果你真的需要它,至少,语言不会让你失望.
在我看来,你的错误是你混合操作员:
1974/01/06AD ^ ^ ^
这是无法避免的,因为/作为运算符,编译器必须解释它.而且,AFAIK,这是一件好事.
为了找到问题的解决方案,我会以其他方式编写文字.例如:
"1974-01-06"_AD ; // ISO-like notation "06/01/1974"_AD ; // french-date-like notation "jan 06 1974"_AD ; // US-date-like notation 19740106_AD ; // integer-date-like notation
就个人而言,我会选择整数和ISO日期,但这取决于您的需求.这是让用户定义自己的文字名称的重点.
这是一个使用用户定义的文字而不是构造函数调用的优点:
#include#include template struct checkbits { static const bool valid = false; }; template struct checkbits { static const bool valid = (High == '0' || High == '1') && checkbits ::valid; }; template struct checkbits { static const bool valid = (High == '0' || High == '1'); }; template inline constexpr std::bitset operator"" _bits() noexcept { static_assert(checkbits ::valid, "invalid digit in binary string"); return std::bitset ((char []){Bits..., '\0'}); } int main() { auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits; std::cout << bits << std::endl; std::cout << "size = " << bits.size() << std::endl; std::cout << "count = " << bits.count() << std::endl; std::cout << "value = " << bits.to_ullong() << std::endl; // This triggers the static_assert at compile time. auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits; // This throws at run time. std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits"); }
优点是运行时异常转换为编译时错误.您无法将静态断言添加到采用字符串的bitset ctor(至少没有字符串模板参数).
这对数学代码来说非常好.出于我的想法,我可以看到以下运算符的用法:
度为度.这使得写绝对角度更加直观.
double operator ""_deg(long double d) { // returns radians return d*M_PI/180; }
它还可以用于各种定点表示(它们仍在DSP和图形领域中使用).
int operator ""_fix(long double d) { // returns d as a 1.15.16 fixed point number return (int)(d*65536.0f); }
这些看起来像是如何使用它的好例子.它们有助于使代码中的常量更具可读性.这是使代码不可读的另一种工具,但是我们已经有太多的工具滥用,而且还有一个不会受到太大伤害.
UDL是命名空间的(并且可以通过使用声明/指令来导入,但是你不能明确命名文字的名称3.14std::i
),这意味着(希望)不会有大量的冲突.
事实上它们实际上可以被模板化(并且constexpr)意味着你可以用UDL做一些非常强大的东西.Bigint的作者会非常高兴,因为他们最终可以拥有任意大的常量,在编译时计算(通过constexpr或模板).
我只是难过,我们将不会看到标准(从外观上来看)一对夫妇有用的文字,喜欢s
的std::string
和i
为虚数单位.
UDL将节省的编码时间实际上并不高,但可读性将大大增加,并且越来越多的计算可以转移到编译时以便更快地执行.
让我补充一点上下文.对于我们的工作,非常需要用户定义的文字.我们致力于MDE(模型驱动工程).我们想用C++定义模型和元模型.我们实际上实现了从Ecore到C++(EMF4CPP)的映射.
当能够在C++中将模型元素定义为类时,就会出现问题.我们正在采用将元模型(Ecore)转换为带参数的模板的方法.模板的参数是类型和类的结构特征.例如,具有两个int属性的类将类似于:
typedef ::ecore::Class< Attribute, Attribute > MyClass;
然而,事实证明,模型或元模型中的每个元素通常都有一个名称.我们想写:
typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;
但是,C++,C++ 0x不允许这样做,因为字符串被禁止作为模板的参数.你可以用char写出char这个名字,但这实在是一团糟.通过适当的用户定义文字,我们可以编写类似的东西.假设我们使用"_n"来识别模型元素名称(我不使用确切的语法,只是为了提出一个想法):
typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute> MyClass;
最后,将这些定义作为模板有助于我们设计用于遍历模型元素,模型转换等的算法,这些算法非常有效,因为类型信息,标识,转换等由编译器在编译时确定.
Bjarne Stroustrup 在关于类型丰富的接口的第一部分中谈到UDL在这个C++ 11演讲中,大约20分钟.
他对UDL的基本论点采用了三段论的形式:
"普通"类型,即内置基元类型,只能捕获琐碎的类型错误.具有更丰富类型的接口允许类型系统捕获更多种类的错误.
丰富类型的代码可以捕获的类型错误类型会对实际代码产生影响.(他给出了火星气候轨道器的例子,由于一个重要常数的尺寸误差而臭名昭着失败).
在实际代码中,很少使用单位.人们不使用它们,因为产生运行时计算或内存开销来创建丰富类型的代价太高,并且使用预先存在的C++模板化单元代码是如此丑陋以至于没有人使用它.(根据经验,没有人使用它,即使图书馆已经存在了十年).
因此,为了让工程师在实际代码中使用单元,我们需要一种设备(1)不会产生运行时开销,(2)在符号上是可接受的.
支持编译时维度检查是唯一需要的理由.
auto force = 2_N; auto dx = 2_m; auto energy = force * dx; assert(energy == 4_J);
例如,请参见PhysUnits-CT-Cpp11,这是一个小型C++ 11,C++ 14头文件库,用于编译时维度分析和单位/数量操作和转换.比Boost.Units简单,支持单位符号文字,如m,g,s,公制前缀,如m,k,M,仅取决于标准C++库,仅SI,维度的整数幂.
嗯......我还没有考虑过这个功能.你的样本经过深思熟虑,肯定很有趣.C++现在非常强大,但不幸的是,您阅读的代码片段中使用的语法有时过于复杂.可读性(如果不是全部的话)至少是那么多.这样的功能可以提高可读性.如果我采取你的最后一个例子
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds
......我想知道你今天是如何表达的.你有一个KG和一个LB类,你要比较隐式对象:
assert(KG(1.0f) == LB(2.2f));
那也行.对于具有较长名称或类型的类型,您不希望为编写适配器的sans提供如此好的构造函数,它可能是即时隐式对象创建和初始化的一个很好的补充.另一方面,您也可以使用方法创建和初始化对象.
但我同意尼尔斯的数学观点.例如,C和C++三角函数需要以弧度为单位输入.我认为在学位上,所以像Nils这样的非常短暂的隐式转换非常好.
最终,它将是语法糖,但它会对可读性产生轻微影响.并且写一些表达式也可能更容易(sin(180.0deg)比sin更容易写(deg(180.0)).然后会有人滥用这个概念.但是,语言滥用的人应该使用非常严格的语言,而不是像C++那样富有表现力的语言.
啊,我的帖子基本上没什么,除了:它会好的,影响不会太大.我们不用担心.:-)