用户定义的文字为 C++ 添加了哪些新功能?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/237804/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:58:50  来源:igfitidea点击:

What new capabilities do user-defined literals add to C++?

c++c++11user-defined-literals

提问by Motti

C++11introduces user-defined literalswhich will allow the introduction of new literal syntax based on existing literals (int, hex, string, float) so that any type will be able to have a literal presentation.

C++11引入了用户定义的文字,这将允许基于现有文字(int, hex, string, float)引入新的文字语法,以便任何类型都能够具有文字表示。

Examples:

例子:

// 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

At first glance this looks very cool but I'm wondering how applicable it really is, when I tried to think of having the suffixes _ADand _BCcreate dates I found that it's problematic due to operator order. 1974/01/06_ADwould first evaluate 1974/01(as plain ints) and only later the 06_AD(to say nothing of August and September having to be written without the 0for octal reasons). This can be worked around by having the syntax be 1974-1/6_ADso that the operator evaluation order works but it's clunky.

乍一看,这看起来很酷,但我想知道它到底有多适用,当我试图考虑使用后缀_AD_BC创建日期时,我发现由于操作员顺序而存在问题。1974/01/06_AD将首先评估1974/01(作为简单的ints),然后才评估(06_AD更不用说八月和九月的八月和九月0由于八进制原因必须在没有写的情况下)。这可以通过使用语法来解决,1974-1/6_AD以便运算符评估顺序起作用,但它很笨重。

So what my question boils down to is this, do you feel this feature will justify itself? What other literals would you like to define that will make your C++ code more readable?

所以我的问题归结为这个,你觉得这个功能会证明自己吗?您想定义哪些其他文字来使您的 C++ 代码更具可读性?



Updated syntax to fit the final draft on June 2011

更新语法以适应 2011 年 6 月的最终草案

采纳答案by emsr

Here's a case where there is an advantage to using user-defined literals instead of a constructor call:

下面是使用用户定义的文字而不是构造函数调用的优势的情况:

#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..., '
// 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 ;
'}); } 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"); }

The advantage is that a run-time exception is converted to a compile-time error. You couldn't add the static assert to the bitset ctor taking a string (at least not without string template arguments).

优点是运行时异常被转换为编译时错误。您无法将静态断言添加到采用字符串的位集构造函数(至少在没有字符串模板参数的情况下不能)。

回答by paercebal

At first sight, it seems to be simple syntactic sugar.

乍一看,它似乎是简单的语法糖。

But when looking deeper, we see it's more than syntactic sugar, as it extends the C++ user's options to create user-defined types that behave exactly like distinct built-in types.In this, this little "bonus" is a very interesting C++11 addition to C++.

但是当深入观察时,我们会发现它不仅仅是语法糖,因为它扩展了 C++ 用户的选择,以创建行为与不同内置类型完全相同的用户定义类型。在这一点上,这个小小的“奖励”是 C++11 对 C++ 的一个非常有趣的补充。

Do we really need it in C++?

我们真的需要在 C++ 中使用它吗?

I see few uses in the code I wrote in the past years, but just because I didn't use it in C++ doesn't mean it's not interesting for another C++ developer.

我在过去几年编写的代码中看到很少使用,但仅仅因为我没有在 C++ 中使用它并不意味着它对另一个 C++ 开发人员不感兴趣。

We had used in C++ (and in C, I guess), compiler-defined literals, to type integer numbers as short or long integers, real numbers as float or double (or even long double), and character strings as normal or wide chars.

我们曾在 C++(我猜也是在 C 中)使用编译器定义的文字,将整数输入为短整数或长整数,将实数输入为浮点数或双精度数(甚至长双精度数),将字符串输入为普通字符或宽字符.

In C++, we had the possibility to create our own types(i.e. classes), with potentially no overhead (inlining, etc.). We had the possibility to add operators to their types, to have them behave like similar built-in types, which enables C++ developers to use matrices and complex numbers as naturally as they would have if these have been added to the language itself. We can even add cast operators (which is usually a bad idea, but sometimes, it's just the right solution).

在 C++ 中,我们有可能创建我们自己的类型(即类),而没有潜在的开销(内联等)。我们有可能在它们的类型中添加运算符,让它们表现得像类似的内置类型,这使 C++ 开发人员能够像将矩阵和复数添加到语言本身一样自然地使用矩阵和复数。我们甚至可以添加强制转换运算符(这通常是一个坏主意,但有时,它只是正确的解决方案)。

We still missed one thing to have user-types behave as built-in types: user-defined literals.

我们仍然错过了让用户类型表现为内置类型的一件事:用户定义的文字。

So, I guess it's a natural evolution for the language, but to be as complete as possible: "If you want to create a type, and you want it to behave as much possible as a built-in types, here are the tools..."

所以,我想这是语言的自然演变,但要尽可能完整:“如果你想创建一个类型,并且你希望它尽可能像内置类型一样表现,这里是工具。 ..

I'd guess it's very similar to .NET's decision to make every primitive a struct, including booleans, integers, etc., and have all structs derive from Object. This decision alone puts .NET far beyond Java's reach when working with primitives, no matter how much boxing/unboxing hacks Java will add to its specification.

我猜这与 .NET 的决定非常相似,即让每个原语都成为一个结构体,包括布尔值、整数等,并且所有结构体都从 Object 派生。无论 Java 将在其规范中添加多少装箱/拆箱技巧,仅这一决定就使 .NET 在使用原语时远远超出了 Java 的范围。

Do YOU really need it in C++?

你真的需要在 C++ 中使用它吗?

This question is for YOUto answer. Not Bjarne Stroustrup. Not Herb Sutter. Not whatever member of C++ standard committee. This is why you have the choice in C++, and they won't restrict a useful notation to built-in types alone.

这个问题是来回答的。不是 Bjarne Stroustrup。不是赫伯·萨特。不是 C++ 标准委员会的任何成员。这就是为什么您可以在 C++ 中进行选择,并且它们不会将有用的符号限制为单独的内置类型。

If youneed it, then it is a welcome addition. If youdon't, well... Don't use it. It will cost you nothing.

如果需要它,那么它是一个受欢迎的补充。如果不这样做,那么......不要使用它。它不会花费你什么。

Welcome to C++, the language where features are optional.

欢迎使用 C++,该语言的功能是可选的。

Bloated??? Show me your complexes!!!

臃肿???给我看看你的情结!!!

There is a difference between bloated and complex (pun intended).

臃肿和复杂(双关语)之间存在差异。

Like shown by Niels at What new capabilities do user-defined literals add to C++?, being able to write a complex number is one of the two features added "recently" to C and C++:

就像 Niels 在C++ 中用户定义的文字添加哪些新功能?,能够写一个复数是“最近”添加到 C 和 C++ 的两个特性之一:

Point p = 25_x + 13_y + 3_z ; // 3D point

Now, both C99 "double complex" type and C++ "std::complex" type are able to be multiplied, added, subtracted, etc., using operator overloading.

现在,C99“双复数”类型和C++“std::complex”类型都可以使用运算符重载进行乘法、加法、减法等操作。

But in C99, they just added another type as a built-in type, and built-in operator overloading support. And they added another built-in literal feature.

但是在 C99 中,他们只是添加了另一种类型作为内置类型,并且内置了运算符重载支持。他们添加了另一个内置的文字功能。

In C++, they just used existing features of the language, saw that the literal feature was a natural evolution of the language, and thus added it.

在 C++ 中,他们只是使用语言的现有特性,看到文字特性是语言的自然演变,因此添加了它。

In C, if you need the same notation enhancement for another type, you're out of luck until your lobbying to add your quantum wave functions (or 3D points, or whatever basic type you're using in your field of work) to the C standard as a built-in type succeeds.

在 C 中,如果您需要对另一种类型进行相同的符号增强,那么在您游说将量子波函数(或 3D 点,或您在工作领域中使用的任何基本类型)添加到作为内置类型的 C 标准成功了。

In C++11, you just can do it yourself:

在 C++11 中,你可以自己做:

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 !

Is it bloated? No, the need is there, as shown by how both C and C++ complexes need a way to represent their literal complex values.

是不是肿了?不,需要在那里,正如 C 和 C++ 复合体如何需要一种方法来表示它们的文字复合值所示。

Is it wrongly designed? No, it's designed as every other C++ feature, with extensibility in mind.

是不是设计错了?不,它被设计为所有其他 C++ 功能,并考虑到可扩展性。

Is it for notation purposes only? No, as it can even add type safety to your code.

它仅用于符号目的吗?不,因为它甚至可以为您的代码添加类型安全。

For example, let's imagine a CSS oriented code:

例如,让我们想象一个面向 CSS 的代码:

1974/01/06AD
    ^  ^  ^

It is then very easy to enforce a strong typing to the assignment of values.

然后很容易对值的分配强制执行强类型。

Is is dangerous?

是危险的吗?

Good question. Can these functions be namespaced? If yes, then Hymanpot!

好问题。这些函数可以命名空间吗?如果是,那么大奖!

Anyway, like everything, you can kill yourself if a tool is used improperly. C is powerful, and you can shoot your head off if you misuse the C gun. C++ has the C gun, but also the scalpel, the taser, and whatever other tool you'll find in the toolkit. You can misuse the scalpel and bleed yourself to death. Or you can build very elegant and robust code.

无论如何,就像所有事情一样,如果工具使用不当,您可能会自杀。C很强大,如果你滥用C枪,你可以开枪。C++ 有 C 枪,但也有手术刀、泰瑟枪,以及您可以在工具包中找到的任何其他工具。您可以滥用手术刀并流血致死。或者您可以构建非常优雅和健壮的代码。

So, like every C++ feature, do you really need it? It is the question you must answer before using it in C++. If you don't, it will cost you nothing. But if you do really need it, at least, the language won't let you down.

那么,就像每个 C++ 特性一样,你真的需要它吗?这是您在 C++ 中使用它之前必须回答的问题。如果你不这样做,它不会花费你什么。但如果你真的需要它,至少,语言不会让你失望。

The date example?

日期示例?

Your error, it seems to me, is that you are mixing operators:

在我看来,您的错误是您正在混合运算符:

"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

This can't be avoided, because / being an operator, the compiler must interpret it. And, AFAIK, it is a good thing.

这是无法避免的,因为 / 作为运算符,编译器必须对其进行解释。而且,AFAIK,这是一件好事。

To find a solution for your problem, I would write the literal in some other way. For example:

为了为您的问题找到解决方案,我会以其他方式编写文字。例如:

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

Personally, I would choose the integer and the ISO dates, but it depends on YOUR needs. Which is the whole point of letting the user define its own literal names.

就个人而言,我会选择整数和 ISO 日期,但这取决于您的需求。这就是让用户定义自己的文字名称的重点。

回答by Nils Pipenbrinck

It's very nice for mathematical code. Out of my mind I can see the use for the following operators:

对于数学代码来说非常好。在我看来,我可以看到以下运算符的用途:

deg for degrees. That makes writing absolute angles much more intuitive.

度数。这使得书写绝对角度更加直观。

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

It can also be used for various fixed point representations (which are still in use in the field of DSP and graphics).

它也可以用于各种定点表示(在DSP和图形领域仍在使用)。

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

These look like nice examples how to use it. They help to make constants in code more readable. It's another tool to make code unreadable as well, but we already have so much tools abuse that one more does not hurt much.

这些看起来像是如何使用它的好例子。它们有助于使代码中的常量更具可读性。它也是使代码不可读的另一种工具,但我们已经有太多的工具滥用,再多一个也不会造成太大伤害。

回答by coppro

UDLs are namespaced (and can be imported by using declarations/directives, but you cannot explicitly namespace a literal like 3.14std::i), which means there (hopefully) won't be a ton of clashes.

UDL 是命名空间的(并且可以通过使用声明/指令导入,但您不能显式命名像 那样的文字3.14std::i),这意味着(希望)不会有大量冲突。

The fact that they can actually be templated (and constexpr'd) means that you can do some pretty powerful stuff with UDLs. Bigint authors will be really happy, as they can finally have arbitrarily large constants, calculated at compile time (via constexpr or templates).

事实上,它们可以被模板化(和 constexpr'd)意味着你可以用 UDL 做一些非常强大的东西。Bigint 作者会非常高兴,因为他们最终可以拥有任意大的常量,在编译时计算(通过 constexpr 或模板)。

I'm just sad that we won't see a couple useful literals in the standard (from the looks of it), like sfor std::stringand ifor the imaginary unit.

我很遗憾我们不会在标准中看到几个有用的文字(从它的外观来看),比如sforstd::stringifor 虚数单位。

The amount of coding time that will be saved by UDLs is actually not that high, but the readability will be vastly increased and more and more calculations can be shifted to compile-time for faster execution.

UDL 节省的编码时间实际上并没有那么高,但是可读性会大大增加,并且越来越多的计算可以转移到编译时以加快执行速度。

回答by Diego Sevilla

Let me add a little bit of context. For our work, user defined literals is much needed. We work on MDE (Model-Driven Engineering). We want to define models and metamodels in C++. We actually implemented a mapping from Ecore to C++ (EMF4CPP).

让我补充一点上下文。对于我们的工作,非常需要用户定义的文字。我们致力于 MDE(模型驱动工程)。我们想用 C++ 定义模型和元模型。我们实际上实现了从 Ecore 到 C++ ( EMF4CPP)的映射。

The problem comes when being able to define model elements as classes in C++. We are taking the approach of transforming the metamodel (Ecore) to templates with arguments. Arguments of the template are the structural characteristics of types and classes. For example, a class with two int attributes would be something like:

当能够将模型元素定义为 C++ 中的类时,问题就出现了。我们正在采取将元模型(Ecore)转换为带有参数的模板的方法。模板的参数是类型和类的结构特征。例如,具有两个 int 属性的类将类似于:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

Hoever, it turns out that every element in a model or metamodel, usually has a name. We would like to write:

然而,事实证明模型或元模型中的每个元素通常都有一个名称。我们想写:

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

BUT, C++, nor C++0x don't allow this, as strings are prohibited as arguments to templates. You can write the name char by char, but this is admitedly a mess. With proper user-defined literals, we could write something similar. Say we use "_n" to identify model element names (I don't use the exact syntax, just to make an idea):

但是,C++ 和 C++0x 都不允许这样做,因为字符串被禁止作为模板的参数。您可以逐个字符地编写名称字符,但这无疑是一团糟。使用适当的用户定义文字,我们可以编写类似的东西。假设我们使用“_n”来标识模型元素名称(我没有使用确切的语法,只是为了提出一个想法):

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Finally, having those definitions as templates helps us a lot to design algorithms for traversing the model elements, model transformations, etc. that are really efficient, because type information, identification, transformations, etc. are determined by the compiler at compile time.

最后,将这些定义作为模板帮助我们设计遍历模型元素、模型转换等的算法,这些算法非常有效,因为类型信息、标识、转换等是由编译器在编译时确定的。

回答by masonk

Bjarne Stroustrup talks about UDL's in this C++11 talk, in the first section on type-rich interfaces, around 20 minute mark.

Bjarne Stroustrup 在这个C++11 talk 中谈到了 UDL ,第一部分是关于类型丰富的接口,大约 20 分钟。

His basic argument for UDLs takes the form of a syllogism:

他对 UDL 的基本论证采用三段论的形式:

  1. "Trivial" types, i.e., built-in primitive types, can only catch trivial type errors. Interfaces with richer types allow the type system to catch more kinds of errors.

  2. The kinds of type errors that richly typed code can catch have impact on real code. (He gives the example of the Mars Climate Orbiter, which infamously failed due to a dimensions error in an important constant).

  3. In real code, units are rarely used. People don't use them, because incurring runtime compute or memory overhead to create rich types is too costly, and using pre-existing C++ templated unit code is so notationally ugly that no one uses it. (Empirically, no one uses it, even though the libraries have been around for a decade).

  4. Therefore, in order to get engineers to use units in real code, we needed a device that (1) incurs no runtime overhead and (2) is notationally acceptable.

  1. “平凡”类型,即内置原始类型,只能捕获平凡的类型错误。具有更丰富类型的接口允许类型系统捕获更多类型的错误。

  2. 类型丰富的代码可以捕获的类型错误对实际代码有影响。(他举了火星气候轨道飞行器的例子,由于一个重要常数的尺寸错误而臭名昭著地失败了)。

  3. 在实际代码中,很少使用单位。人们不使用它们,因为产生丰富类型的运行时计算或内存开销成本太高,而且使用预先存在的 C++ 模板化单元代码在符号上非常丑陋,以至于没有人使用它。(根据经验,没有人使用它,即使这些库已经存在了十年)。

  4. 因此,为了让工程师在实际代码中使用单元,我们需要一种设备,(1) 不会产生运行时开销,并且 (2) 在符号上是可以接受的。

回答by Martin Moene

Supporting compile-time dimension checking is the only justification required.

支持编译时维度检查是唯一需要的理由。

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

See for example PhysUnits-CT-Cpp11, a small C++11, C++14 header-only library for compile-time dimensional analysis and unit/quantity manipulation and conversion. Simpler than Boost.Units, does support unit symbolliterals such as m, g, s, metric prefixessuch as m, k, M, only depends on standard C++ library, SI-only, integral powers of dimensions.

参见例如PhysUnits-CT-Cpp11,一个小型的 C++11、C++14 头文件库,用于编译时维度分析和单位/数量操作和转换。比Boost.Units更简单,确实支持单位符号文字如 m、g、s、公制前缀如 m、k、M,仅依赖于标准 C++ 库,仅 SI,维度的整数幂。

回答by mstrobl

Hmm... I have not thought about this feature yet. Your sample was well thought out and is certainly interesting. C++ is very powerful as it is now, but unfortunately the syntax used in pieces of code you read is at times overly complex. Readability is, if not all, then at least much. And such a feature would be geared for more readability. If I take your last example

嗯...我还没有想过这个功能。您的样品经过深思熟虑,当然很有趣。C++ 现在非常强大,但不幸的是,您阅读的代码片段中使用的语法有时过于复杂。可读性是,如果不是全部,那么至少是很多。而这样的功能将旨在提高可读性。如果我拿你最后一个例子

assert(KG(1.0f) == LB(2.2f));

... I wonder how you'd express that today. You'd have a KG and a LB class and you'd compare implicit objects:

......我想知道你今天会如何表达。你有一个 KG 和一个 LB 类,你会比较隐式对象:

 "asd##代码####代码####代码##"_b

And that would do as well. With types that have longer names or types that you have no hopes of having such a nice constructor for sans writing an adapter, it might be a nice addition for on-the-fly implicit object creation and initialization. On the other hand, you can already create and initialize objects using methods, too.

那也行。对于具有更长名称或类型的类型,您不希望拥有如此出色的构造函数来编写适配器,这可能是动态隐式对象创建和初始化的一个很好的补充。另一方面,您也可以使用方法创建和初始化对象。

But I agree with Nils on mathematics. C and C++ trigonometry functions for example require input in radians. I think in degrees though, so a very short implicit conversion like Nils posted is very nice.

但我同意 Nils 在数学上的看法。例如,C 和 C++ 三角函数需要以弧度为单位的输入。不过我认为是度数,所以像 Nils 发布的非常短的隐式转换非常好。

Ultimately, it's going to be syntactic sugar however, but it will have a slight effect on readability. And it will probably be easier to write some expressions too (sin(180.0deg) is easier to write than sin(deg(180.0)). And then there will be people who abuse the concept. But then, language-abusive people should use very restrictive languages rather than something as expressive as C++.

最终,它将是语法糖,但它会对可读性产生轻微影响。而且可能会更容易写一些表达式(sin(180.0deg) 比 sin(deg(180.0) 更容易写)。然后会有人滥用这个概念。但是,语言滥用的人应该使用非常严格的语言,而不是像 C++ 那样具有表现力的语言。

Ah, my post says basically nothing except: it's going to be okay, the impact won't be too big. Let's not worry. :-)

啊,我的帖子基本上没有说什么,除了:会没事的,影响不会太大。我们不要担心。:-)

回答by fizzer

I have never needed or wanted this feature (but this could be the Blubeffect). My knee jerk reaction is that it's lame, and likely to appeal to the same people who think that it's cool to overload operator+ for any operation which could remotely be construed as adding.

我从来不需要或想要这个功能(但这可能是Blub效果)。我的下意识反应是它很蹩脚,并且可能会吸引那些认为为任何可以远程解释为添加的操作重载 operator+ 很酷的人。

回答by rr-

I used user literals for binary strings like this:

我将用户文字用于二进制字符串,如下所示:

##代码##

using std::string(str, n)constructor so that \0wouldn't cut the string in half. (The project does a lot of work with various file formats.)

使用std::string(str, n)构造函数,\0这样就不会将字符串切成两半。(该项目对各种文件格式做了很多工作。)

This was helpful also when I ditched std::stringin favor of a wrapper for std::vector.

当我放弃std::string支持std::vector.