IT story

사용자 정의 리터럴은 C ++에 어떤 새로운 기능을 추가합니까?

hot-time 2020. 6. 22. 07:38
반응형

사용자 정의 리터럴은 C ++에 어떤 새로운 기능을 추가합니까?


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) 평가 하고 나중에 06_AD(8 월과 9 월 0을 8 진수 이유 없이 쓰지 않아도 됨) 말입니다 . 이것은 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");
}

장점은 런타임 예외가 컴파일 타임 오류로 변환된다는 것입니다. 문자열을 가져 오는 비트 세트 ctor에 정적 어설 션을 추가 할 수 없습니다 (적어도 문자열 템플릿 인수가없는 경우).


첫눈에 간단한 구문 설탕 인 것 같습니다.

그러나 더 깊게 살펴보면 C ++ 사용자 옵션을 확장하여 고유 한 내장 유형과 똑같이 동작하는 사용자 정의 유형을 생성 하므로 구문 설탕 이상의 의미 가 있습니다. 이 작은 "보너스"는 C ++에 대한 흥미로운 C ++ 11 추가입니다.

C ++로 정말로 필요합니까?

지난 몇 년 동안 작성한 코드에서 몇 가지 용도를 보았지만 C ++에서 사용하지 않았다고해서 다른 C ++ 개발자 에게는 흥미롭지 않다는 의미는 아닙니다 .

우리는 컴파일러 정의 리터럴 인 C ++ (및 C)에서 정수를 짧은 정수 나 긴 정수로, 실수는 실수를 float 나 double (또는 long double)로, 그리고 문자열을 보통 또는 넓은 문자로 입력했습니다. .

C ++에서는 오버 헤드 (인라인 등)없이 자체 유형 (예 : 클래스) 을 만들 수있었습니다 . 우리는 연산자를 유형에 추가하고 유사한 내장 유형처럼 작동하도록 할 수 있었으므로 C ++ 개발자는 언어 자체에 행렬과 복잡한 숫자를 추가했을 때와 마찬가지로 자연스럽게 사용할 수 있습니다. 캐스트 연산자를 추가 할 수도 있습니다 (보통 나쁜 생각이지만 때로는 올바른 솔루션 일 수도 있습니다).

우리는 여전히 사용자 유형이 내장 유형으로 동작하도록하는 한 가지를 놓쳤다 : 사용자 정의 리터럴.

따라서 저는 언어가 자연스럽게 진화 한 것 같지만 가능한 한 완전해야한다고 생각합니다. " 유형을 만들고 내장 유형만큼 가능한 한 동작하려면 여기 도구가 있습니다. .. "

부울, 정수 등 모든 프리미티브를 구조체로 만들고 모든 구조체가 Object에서 파생되도록하는 것이 .NET의 결정과 매우 유사하다고 생각합니다. 이 결정만으로도 Java가 사양에 추가 할 boxing / unboxing 해킹의 양에 관계없이 .NET은 프리미티브로 작업 할 때 Java의 범위를 훨씬 뛰어 넘습니다.

C ++로 정말로 필요합니까?

이 질문은 당신 이 대답 하기위한 것입니다. Bjarne Stroustrup이 아닙니다. 허브 셔터가 아닙니다. C ++ 표준위원회의 멤버는 아닙니다. 이것이 C ++에서 선택하는 이유 이며 유용한 표기법을 내장 유형으로 제한하지 않습니다.

경우 당신이 그것을 필요, 그것은 환영의 추가이다. 경우 당신이 하지, 음 ... 그것을 사용하지 마십시오. 비용이 들지 않습니다.

기능이 선택적인 언어 인 C ++에 오신 것을 환영합니다.

부푼??? 당신의 단지를 보여줘!

부풀어 오른 것과 복잡 한 것 (pun 의도 된 것)에는 차이가 있습니다.

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 "이중 복소수"유형과 C ++ "std :: complex"유형은 연산자 오버로딩을 사용하여 곱하거나 더하거나 빼는 등의 작업을 수행 할 수 있습니다.

그러나 C99에서는 다른 유형을 내장 유형 및 내장 연산자 과부하 지원으로 추가했습니다. 그리고 또 다른 내장 리터럴 기능을 추가했습니다.

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.

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

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

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.

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

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::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 !

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 Jackpot!

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.

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.

The date example?

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

1974/01/06AD
    ^  ^  ^

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

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

"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

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.


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.

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

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

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

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.


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.

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).

I'm just sad that we won't see a couple useful literals in the standard (from the looks of it), like s for std::string and i for the imaginary unit.

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.


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).

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:

typedef ::ecore::Class< Attribute<int>, Attribute<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", Attribute< "x", int>, Attribute<"y", 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):

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

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.


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

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

  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.


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

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

assert(energy == 4_J); 

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 symbol literals such as m, g, s, metric prefixes such as m, k, M, only depends on standard C++ library, SI-only, integral powers of dimensions.


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

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

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

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

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.

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++.

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


I have never needed or wanted this feature (but this could be the Blub effect). 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.


C++ is usually very strict about the syntax used - barring the preprocessor there is not much you can use to define a custom syntax/grammar. E.g. we can overload existing operatos, but we cannot define new ones - IMO this is very much in tune with the spirit of C++.

I don't mind some ways for more customized source code - but the point chosen seems very isolated to me, which confuses me most.

Even intended use may make it much harder to read source code: an single letter may have vast-reaching side effects that in no way can be identified from the context. With symmetry to u, l and f, most developers will choose single letters.

This may also turn scoping into a problem, using single letters in global namespace will probably be considered bad practice, and the tools that are supposed mixing libraries easier (namespaces and descriptive identifiers) will probably defeat its purpose.

I see some merit in combination with "auto", also in combination with a unit library like boost units, but not enough to merit this adition.

I wonder, however, what clever ideas we come up with.


I used user literals for binary strings like this:

 "asd\0\0\0\1"_b

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

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


Line noise in that thing is huge. Also it's horrible to read.

Let me know, did they reason that new syntax addition with any kind of examples? For instance, do they have couple of programs that already use C++0x?

For me, this part:

auto val = 3.14_i

Does not justify this part:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

Not even if you'd use the i-syntax in 1000 other lines as well. If you write, you probably write 10000 lines of something else along that as well. Especially when you will still probably write mostly everywhere this:

std::complex<double> val = 3.14i

'auto' -keyword may be justified though, only perhaps. But lets take just C++, because it's better than C++0x in this aspect.

std::complex<double> val = std::complex(0, 3.14);

It's like.. that simple. Even thought all the std and pointy brackets are just lame if you use it about everywhere. I don't start guessing what syntax there's in C++0x for turning std::complex under complex.

complex = std::complex<double>;

That's perhaps something straightforward, but I don't believe it's that simple in C++0x.

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

Perhaps? >:)

Anyway, the point is: writing 3.14i instead of std::complex(0, 3.14); does not save you much time in overall except in few super special cases.

참고URL : https://stackoverflow.com/questions/237804/what-new-capabilities-do-user-defined-literals-add-to-c

반응형