What new capabilities do user-defined literals add

2019-01-03 00:44发布

C++11 introduces user-defined literals which 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.

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 _AD and _BC create dates I found that it's problematic due to operator order. 1974/01/06_AD would 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 0 for octal reasons). This can be worked around by having the syntax be 1974-1/6_AD so that the operator evaluation order works but it's clunky.

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?


Updated syntax to fit the final draft on June 2011

12条回答
Melony?
2楼-- · 2019-01-03 01:12

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.

查看更多
一夜七次
3楼-- · 2019-01-03 01:16

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.

查看更多
放荡不羁爱自由
4楼-- · 2019-01-03 01:19

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.

查看更多
叛逆
5楼-- · 2019-01-03 01:20

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

查看更多
The star\"
6楼-- · 2019-01-03 01:21

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.

查看更多
一纸荒年 Trace。
7楼-- · 2019-01-03 01:23

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

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

查看更多
登录 后发表回答