Sonkeng ::

Git Evangelist,C++ Alchemist,
Engineering Africa

:: C++ User Defined Literals


Posted on by sdmg15

The advent of C++11 introduced a lot of new awesome features into the language going from Lambda Expressions, Exception Specification with noexcept, Constant Expressions, auto type specifier to User Defined Literal and more… I’ve already covered the basics of Lambda in my previous post, if you haven’t read it yet I recommend you to. Today what will get our attention are UDLs. UDL or User Defined Literal for long is a C++ feature that helps you provide some shorthand construction of your object types, to the user of your interface.

The language itself comes in with some predefined literal suffixes suchs as sv (String View) which lets you contruct a string view from a string literal e.g auto myStringView = "This is a new string view"sv; In order to be able to use the sv literal you’ll need to use the required namespace using namespace std::literals.

You could also write custom suffixes for your custom types e.g auto myType = "some string for my type"_mytype; that’s where this post aim to drive you through.

The language has a ton of predefined literals that we won’t cover here but instead will see how to write ours. But before being able to do so there are some few rules as usually that we need to be aware of. Ready? Let’s go …

The syntax and supported parameter types

As during each war, if you want to have a chance to come out winner, you need to get prepared well, this is what this section is all about means preparing our weapons !

The Syntax

The syntax of UDLs is almost similar to the one you use when declaring a function or a function template operators. The defined operator is called Literal Operator or Literal Operator Template if it’s a template.

Yes man thanks for the info, but please dissimulate the doubt in me…

Hehe yeah let’s move straight to it. Considering that it follows the same rules as normal functions declaration we’ll omit return type specification and other specifiers to just focus on what interest us.

The global syntax follows this pattern:

1
operator ""_ud-suffix(ParamType)

Where :

  • _ud-suffix is a character sequence preceeded by an underscore (_) that will constitute your actual customized prefix. eg: auto operator ""_myType(ParamType)
  • ParamType is the type that will be used when the compiler will be perfoming the unqualified lookup to match the viable overloaded operator.

The values that ParamType supports are discussed in the next section. The syntax is so simple right? Now let’s see what are the values supported by ParamType to complete our cake.

Supported parameter types

As stated earlier, UDL will let you produce objects of your defined type by providing a custom suffix. The only literal types that are allowed before your suffix are integer literal, character literal, string literal and floating point literal.

1
2
3
4
auto distance = 10_km; // Integer Literal
auto monthObj = "january"_month; // String Literal
auto weight = 10.49_kg; // Floating Point Literal
auto asciiCode = 'C'_ascii; // Character Literal

For every types in the previous listing, there should be a corresponding overloaded operator defined, else the program will be ill-formed:

  • For user defined integer literals, the compiler will perfom an unqualified lookup and if the overload set includes a definition with ParamType equals to unsigned long long this overload will be selected and the user defined literal expression will be considered as a call to the function operator "" myType(xULL) where x is the literal without the ud-suffix. eg:

Let’s suppose we are in a stock market and we want to place some orders to buy 100 shares of an asset:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <type_traits>

enum class Side{
    BUY = 1,
    SELL = 2
};

struct Order {
    explicit Order(int qty, Side side = Side::BUY) : qty_(qty), side_(side) {}
    int qty_;
    Side side_;
};

auto operator ""_order(unsigned long long qty) {
    return Order(qty);
}
 
auto main() -> int {
 auto newBuyOrder = 100_order;
 std::cout << newBuyOrder.qty_ <<"\n";
}

Compiler Explorer

We can interpret the line 20 as auto newBuyOrder = operator "" order(100ULL).

  • For user defined floating point literals, the compiler will still perfom the unqualified lookup and if the overload set includes a definition with ParamType equals to long double this overload will be selected and the user defined literal expression will be considered as a call to the function operator "" myType(fL) where f is the literal without the ud-suffix
1
2
3
4
5
6
auto operator ""_kg(long double amount) {
    // Construct some object here
}
auto main() -> int {
auto weight = 10.10_kg;
}

We can interpret the line 5 as auto weight = operator "" kg(10.10L).

  • For user defined character literals (all its variant included: wchar_t, char8_t etc.) a corresponding overload with the variant type need to be defined, except the case where some can fit into others.
1
2
3
4
5
6
7
8
9
auto  operator  ""_p(char c) {
	return Person{10, c};
}

auto main() -> int {
    auto p = 'C'_p; // will call operator "" p(C)
    auto p2 = u8'P'_p; // compiles, p2 type is char until C++20
    auto p3 = L'P3'_p; // Will fail, need to include wchar_t in the overload set
}
  • For user defined string literals (all its variants included, const wchar_t*, const char16_t etc) an overload following the form
1
operator ""_ud-suffix(Type, std::size_t)

need to be defined. Where Type is one of the variant and std::size_t is the size of the string excluding the terminating null character \0.

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream> 

struct Person{
    Person(const char* name, std::size_t len){}
};
auto operator ""_p(const char* name, std::size_t len){
    return Person{name, len};
}

auto main() -> int {
  auto person = "king arthur"_p;
}

Compiler Explorer

NOTE: For user defined integer literals and floating point literals, if the overload set doesn’t include what is described above, but there is an overload for character literals, that overload will be used as fallback by the compiler otherwise the program will be ill-formed. Let’s see that with an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Money {
    explicit Money(int amount) : amount_(amount){}
    int amount_;
};

auto operator ""_euro(const char* amount) {
    // The amount is a string, cast it to int
    // then construct the object
    return Money( std::stoi(amout) );
}

auto main() -> int {
  // call operator "" euro("100")
  auto money = 100_euro;
}

This line 14 will then correspond to a call to operator "" euro ("100").

You said literal operator template are similar to function template declaration, but I wrote a User Defined Literal operator template and the compiler is yelling at me :sad:

Oh don’t do that, at least without following the rules. The rules for defining literal operator template are as follows:

  • The template parameter should be only one argument which must be a non-type template parameter pack with type char

  • The operator should have empty parameter list, means the function should not take any argument

In terms of code it means the declaration should be of this form template<char...> auto operator ""_P(). The call to such operator will result in operator "" P<'c1', 'c2', 'c3'..., 'ck'>(), where c1..ck are the individual characters of the integer or floating point literal.

Let’s suppose we want to sum all digits of a given number we could write something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    #include <iostream>
    #include <array>
    #include <algorithm>

    template<char... t>
    auto operator"" _sumOfDigits(){
        int r = 0;
        [&](const std::array<char, sizeof...(t)>& a){
            std::for_each(a.begin(), a.end(), [&](char c){
                r += c - '0';
            });
        }({t...});
        return r;
    }

    auto main() -> int {
        auto sum = 912_sumOfDigits;
        std::cout << "The sum of digits is : " << sum << "\n";
    }

The UDL at line 17 will be treated as a function call operator "" sumOfDigits<'9, '1', '2'>().

The defined literal fo string_view (sv) you shown doesn’t have an underscrore at the beginning why does my own has one, are you kidding me ?

Oh good catch! The literals that don’t have the underscore before their suffix are reserved means only the standard library can define such literals alas!

Users and use cases of UDLs

You’ve scrolled enough ! If you reached here means you already know the basics of UDL congrats. But there may be still this question intriguing you: who uses UDLs and when should I use them?

Well, there are a lot of libraries out there that provide UDL for their types. For example nlohmann::json which is a JSON library for modern C++ provides the suffix _json so that you can construct JSON from raw string literals.

1
auto order = R"({"OrdID": "1", "Instrument": "btc_usd"})"_json;

The std::chrono library has also a lot of defined literals to help you construct time and date easily and quickly. Remember to include the required namespace with using namespace std::literals

1
2
auto milliseconds = 10000ms;
auto seconds = 1000s;

UDL comes in a handy way when you’re writting your units tests as stated in this talk by Matt Godbolt (https://youtu.be/nLSm3Haxz0I?t=1149).

I also wrote a toy library called Nkap to demonstrate the usage of UDLs, you can check the source code of the repo https://github.com/sdmg15/nkap.

This is just to name few, you can also go ahead and take advantage of the feature to provide such shorthand objects construction for your types !

Final Thoughts

In the stampede to modern C++, adding UDL to your toolbox will help you write strong programs, keeping you also at the cutting edge. I admit it’s a lot to take in, but recognize with me that the pain is temporary whilst the glorry is eternal :smile: !

References