用户定义的文字给 C++ 添加了哪些新功能?
-
04-07-2019 - |
题
C++11 介绍 用户定义的文字 这将允许基于现有文字引入新的文字语法(int
, hex
, string
, float
)以便任何类型都能够有字面表示。
例子:
// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{
return std::complex<long double>(0, d);
}
auto val = 3.14_i; // val = complex<long double>(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
(更不用说八月和九月必须在没有 0
由于八进制的原因)。这可以通过语法来解决 1974-1/6_AD
这样操作符评估顺序就可以工作,但很笨重。
所以我的问题归结为这个,你觉得这个功能会证明自己是合理的吗?您还想定义哪些其他文字来使您的 C++ 代码更具可读性?
更新语法以适应 2011 年 6 月的最终草案
解决方案
以下是使用用户定义的文字而不是构造函数调用的优点:
#include <bitset>
#include <iostream>
template<char... Bits>
struct checkbits
{
static const bool valid = false;
};
template<char High, char... Bits>
struct checkbits<High, Bits...>
{
static const bool valid = (High == '0' || High == '1')
&& checkbits<Bits...>::valid;
};
template<char High>
struct checkbits<High>
{
static const bool valid = (High == '0' || High == '1');
};
template<char... Bits>
inline constexpr std::bitset<sizeof...(Bits)>
operator"" _bits() noexcept
{
static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
return std::bitset<sizeof...(Bits)>((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(至少没有字符串模板参数)。
其他提示
但是在深入研究时,我们发现它不仅仅是语法糖,因为它扩展了C ++用户的选项来创建用户定义的类型,其行为与不同的内置类型完全相同。在此,小“奖金”是C ++中非常有趣的C ++ 11补充。
我们真的需要用C ++吗?
我在过去几年编写的代码中看到的用处很少,但仅仅因为我没有在C ++中使用它并不意味着它对另一个C ++开发人员没有意义。
我们在C ++中使用(在C中,我猜),编译器定义的文字,用于输入整数作为短整数或长整数,实数表示为float或double(甚至是long double),字符串正常或广泛的角色。
在C ++中,我们有可能创建自己的类型(即类),可能没有开销(内联等)。我们有可能在它们的类型中添加运算符,使它们的行为类似于类似的内置类型,这使得C ++开发人员能够像使用语言本身一样自然地使用矩阵和复数。我们甚至可以添加强制转换操作符(这通常是一个坏主意,但有时候,它只是正确的解决方案)。
我们仍然错过了一件事,让用户类型的行为类似于内置类型:用户定义的文字。
所以,我想这是语言的自然演变,但要尽可能完整:“如果你想创建一个类型,你希望它的行为尽可能像内置一样类型,这里是工具...... &quot;
我猜这与.NET决定使每个原语成为结构(包括布尔值,整数等)非常类似,并且所有结构都来自Object。这个决定使得.NET在使用原语时远远超出Java的范围,无论Java将在规范中添加多少装箱/拆箱黑客。
你真的需要用C ++吗?
此问题仅供您回答。不是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::complex<double> z1(1, 2) ;
// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;
现在,C99“双重复合”都是type和C ++“std :: complex”类型可以使用运算符重载进行相乘,相加,相减等。
但在C99中,他们只是添加了另一种类型作为内置类型,以及内置的运算符重载支持。并且他们添加了另一个内置的文字功能。
在C ++中,他们只是使用了语言的现有功能,看到文字特征是语言的自然演变,因此添加了它。
在C中,如果你需要对另一种类型使用相同的符号增强,那么在你游说添加你的量子波函数(或3D点,或你在工作领域中使用的任何基本类型)之前,你运气不好)作为内置类型的C标准成功。
在C ++ 11中,你可以自己做:
Point p = 25_x + 13_y + 3_z ; // 3D point
臃肿吗?没有,需要存在,如C和C ++复合体如何需要表达方式所示
对于数学代码来说非常好。出于我的想法,我可以看到以下运营商的用途:
度为度。这使得写绝对角度更加直观。
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<int>, Attribute<int> > MyClass;
然而,事实证明,模型或元模型中的每个元素通常都有一个名称。我们想写一下:
typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;
BUT,C ++和C ++ 0x不允许这样做,因为字符串被禁止作为模板的参数。你可以用char写出char这个名字,但这实在是一团糟。通过适当的用户定义文字,我们可以编写类似的东西。假设我们使用“_n”识别模型元素名称(我不使用确切的语法,只是为了提出想法):
typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > 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, metric前缀,如m,k,M,仅取决于标准C ++库,仅SI,维度的整数幂。
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 ++那样富有表现力的语言。
啊,我的帖子基本上没什么,除了:它会好的,影响不会太大。我们不用担心。 : - )
我从未需要或想要此功能(但这可能是 Blub 效果) 。我的膝盖反射是一种蹩脚的反应,并且很可能会吸引那些认为让操作员+超负荷运行的任何操作都很酷的人。
C ++对所使用的语法通常非常严格 - 除了预处理器之外,您可以使用很多来定义自定义语法/语法。例如。我们可以重载现有的operatos,但我们无法定义新的操作符 - IMO这非常符合C ++的精神。
我不介意更多定制源代码的一些方法 - 但选择的点似乎对我来说非常孤立,这让我最困惑。
即使是预期的使用也可能使得阅读源代码变得更加困难:单个字母可能具有广泛的副作用,这些副作用绝不能从上下文中识别出来。对于u,l和f的对称性,大多数开发人员将选择单个字母。
这也可能将范围变成一个问题,在全局命名空间中使用单个字母可能会被认为是不好的做法,并且假设混合库更容易的工具(命名空间和描述性标识符)可能会破坏它的目的。
我看到与“auto”结合使用的一些优点,也与提升单位,但不足以值得这个提议。
然而,我想知道我们提出了哪些聪明的想法。
我将用户文字用于二进制字符串,如下所示:
"asd\0\0\0\1"_b
使用 std :: string(str,n)
构造函数,以便 \ 0
不会将字符串切成两半。 (该项目使用各种文件格式进行了大量工作。)
当我抛弃 std :: string
以支持 std :: vector
的包装时,这也很有用。
那东西的线路噪音很大。读起来也很可怕。
让我知道,他们是否推荐使用任何类型的示例添加新语法?例如,他们有几个已经使用C ++ 0x的程序吗?
对我来说,这部分:
auto val = 3.14_i
不能证明这一部分:
std::complex<double> operator ""_i(long double d) // cooked form
{
return std::complex(0, d);
}
即使您在其他1000行使用i语法也不行。如果你写作,你也可能会写上10000行其他内容。特别是当你仍然可能在所有地方写作时:
std::complex<double> val = 3.14i
'auto'-keyword可能是合理的,但也许只是。但是我们只考虑C ++,因为在这方面它比C ++ 0x更好。
std::complex<double> val = std::complex(0, 3.14);
就像......那么简单。甚至认为如果你在任何地方使用它,所有std和尖括号都只是跛脚。我不会开始猜测在C ++ 0x中使用什么语法来转换复杂的std :: complex。
complex = std::complex<double>;
这可能是直截了当的,但我不相信它在C ++ 0x中那么简单。
typedef std::complex<double> complex;
complex val = std::complex(0, 3.14);
也许? &gt; :)
无论如何,重点是:编写3.14i而不是std :: complex(0,3.14);除了极少数特殊情况外,总体上不会为你节省很多时间。