Creating Your Own UDLs in C++ - dummies

By John Paul Mueller, Jeff Cogswell

The Standard Library, coupled with the built-in features of C++, provide you with an interesting array of literals. However, the true value of literals becomes more obvious when you create your own.

There are many different needs you can address using User-Defined Literals (UDLs), but three common needs are supporting data conversions, making custom types easier to work with, and obtaining desired side effects without the usual number of coding problems.

Although built-in or Standard Library literals come in both prefix and suffix form, you can only create the suffix form when defining your own literals. In addition, the suffix must begin with an underscore. The underscore serves to help prevent conflicts with existing suffixes and to ensure other developers know that the literal is a custom (nonstandard) form.

Developing a conversion UDL

It’s possible to encapsulate conversions within a UDL. All you need to do once you create such a UDL is provide the appropriate suffix when defining the constant to obtain the result you want. The CustomUDL01 example demonstrates a technique for defining a conversion that changes the radius input to the area of a circle in the constant.

#include <iostream>
using namespace std;
constexpr long double operator" _circ ( long double radius )
{
    return radius*radius*3.141592;
}
int main()
{
    double x = 5.0_circ;
    cout << "The circle's area is: " << x << endl;
    return 0;
}

In order to create the UDL, the example relies on a constexpr with a return value of a long double and an input value, radius, of a long double. The equation for computing the area of a circle is πr2. As you can see, the example performs the correct computation as part of the constexpr.

Whenever you create a custom UDL, the compiler forces you to use the largest type for the conversion. What this means is that you must use a long double for floating point literals and unsigned long long for integer literals. Even if you later choose to use a smaller type, as is done in this example by declaring x as a double, the literal itself must employ the largest possible type.

To declare a UDL of the new type, the example creates x, which uses the _circ suffix. It then outputs the result onscreen. When you run this example, you see that the correct value has been placed in x, as shown here:

The circle's area is: 78.5398

Developing a custom type UDL

A lot of the code you encounter relies on custom types that are hard to follow and understand. Creating a UDL to simplify the code makes things clearer and reduces the potential for error. The CustomUDL02 example shows a custom type, the operator used to create the UDL, as well as how the UDL is used to define a literal.

#include <iostream>
using namespace std;
struct MyType
{
    MyType (double Input):Value(Input){}
    double Value;
};
MyType operator" _mytype (long double Value)
{
    return MyType(Value);
}
int main()
{
    auto UDLType = 145.6_mytype;
    cout << UDLType.Value << endl;
    return 0;
}

In order for this technique to work, you must create a constructor for your type that accepts the number of inputs required to configure the type. At minimum, the constructor must accept one type or the input value the user provides is lost.

The custom type need not support the same size data type as required by the operator, but they must be of the same sort. For example, you couldn’t transition a long double to an int.

When you run this example, you see an output value of 145.6, which is the value you input to the custom type. It’s possible to handle fairly complex setups using this approach. The user of your custom type obtains the capability to create clear code that’s easy to follow and interpret, even when the underlying types are complex.

Using a custom UDL for side effects

One of the most interesting uses for UDLs is to create side effects (an operation other than the usual or normal operation, either to make the application shorter and more efficient or to provide added flexibility). You want to define a certain kind of operation that takes place as a result of defining the literal.

What you get is still a literal, but a literal that doesn’t necessarily denote a value that you plan to use later. The CustomUDL03 example shows one such non-traditional use.

#include <iostream>
using namespace std;
void operator" _countdown (unsigned long long Value)
{
    for (int i = Value; i >= 0; i--)
        cout << i << endl;
}
int main()
{
    10_countdown;
    return 0;
}

Notice that the _countdown operator isn’t attached to something that you’d normally associate with a value. In fact, it doesn’t return a value at all. What you get instead is a side effect. When you run this example, you see this output.

10
9
8
7
6
5
4
3
2
1
0

What has happened is that the compiler has replaced 10_countdown with individual cout statements, one for each iteration of the loop. What you end up with is 11 cout statements that output the values between 10 and 0 (in reverse order). The side effect UDL opens all sorts of interesting possibilities for creating code that simplifies certain repetitive tasks in a manner that makes their use obvious.