Understanding C++ User Defined Literals

Devarshi Shimpi - Nov 23 '22 - - Dev Community

The standard library itself comes in with some predefined literal suffixes such 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.

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:

operator ""_ud-suffix(ParamType)
Enter fullscreen mode Exit fullscreen mode

Where :

  • _ud-suffix is a character sequence preceded 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.

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:

#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";
}
Enter fullscreen mode Exit fullscreen mode

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

auto operator ""_kg(long double amount) {
    // Construct some object here
}
auto main() -> int {
auto weight = 10.10_kg;
}
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

For user defined string literals (all its variants included, const wchar_t*, const char16_t etc) an overload following the form
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.

#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;
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
}
Enter fullscreen mode Exit fullscreen mode

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

    #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";
    }
Enter fullscreen mode Exit fullscreen mode

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

The defined literal for string_view (sv) you shown doesn’t have an underscore 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!

Happy Coding!!!

Thank You for reading till here. Meanwhile you can check out my other blog posts and visit my Github.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .