User defined literals

The C++ language defines various built-in literals (numerical, character, string, boolean and pointer) and a series of prefixes and suffixes to specify some of them. The suffix or prefix is part of the literal.

auto b = true;     // boolean
auto s = "sample"; // const char[7]
auto i = 128;      // int
auto d = 128.0;    // double
auto p = nullptr;  // nullptr_t

// with prefixes
auto w = L"text";  // const wchar_t[5]
auto h = 0xBAD;    // int (in hexadecimal representation)

// with suffixes
auto a = 128u;     // unsigned int
auto l = 128l;     // signed long
auto f = 128.0f;   // float

The C++11 standard introduced the possibility to create user-defined literals, that are basically built-in type literals (integer, float, char or string) followed by a used-defined suffix. User-defined literals enable the creation of new objects based on the built-in literal value and the applied user-defined suffix.

auto temp = 77_fah;       // 77 Fahrenheit degrees = 25 Celsius degrees
auto size = 1_KB;         // 1 kilobyte = 1024 bytes
auto emp  = "marius"_dev; // a user defined type Developer 

A bit of theory

A user-defined literal is treated as a call to a literal operator or a literal operator template. User-defined literals have two forms:

  • raw: a sequence of characters; the literal 0xBAD is in raw form is ‘0’, ‘x’, ‘B’, ‘A’, ‘D’
  • cooked: is the compiler interpreted type of the sequence of characters; the literal 0xBAD is the integer 2898 in cooked form.

User-defined literals:

  • support only the suffix form; defining prefixes is not possible;
  • begin with a underscore (‘_’); all suffixes that begin with any other character except underscore are reserved by the standard;
  • can be extended in both raw and cooked form; the exception is represented by strings that can only be extended in the cooked form

Cooked literals

The literal operator called for a cooked literal has following form:

OutputType operator "" _suffix(InputType);

Only several input types are allowed:

  • for integral literals (decimal, octal, hexadecimal or binary) the type is unsigned long long (the reason for unsigned is that the sign is not part of a numeric literal, but is in fact a unary operator applied to the numerical value).
    OutputType operator "" _suffix(unsigned long long);
    
  • for floating point types the type is long double:
    OutputType operator "" _suffix(long double);
    
  • for characters the type is char, wchar_t, char16_t or char32_t:
    OutputType operator "" _suffix(char);
    OutputType operator "" _suffix(wchar_t);
    OutputType operator "" _suffix(char16_t);
    OutputType operator "" _suffix(char32_t);
    
  • for strings the type is char const *, wchar_t const *, char16_t const * or char32_t const *:
    OutputType operator "" _suffix(char const *, std::size_t);
    OutputType operator "" _suffix(wchar_t const *, std::size_t);
    OutputType operator "" _suffix(char16_t const *, std::size_t);
    OutputType operator "" _suffix(char32_t const *, std::size_t);
    

Raw literals

Raw literals are supported only for integral and floating point types. The literal operator called for a cooked literal has following form (notice that the operator does not take a second parameter to indicate the size, the string is null-terminated):

OutputType operator "" _suffix(const char*);

Parsing this array of characters may involve loops, variable declaration, function calls, etc. As a result this form of the literal operator cannot be constexpr, which means it cannot be evaluated at compile time.

An alternative way of processing raw literals is with a literal operator variadic template. The purpose of a variadic template literal operator is to make the literal transformation at compile time. The form of the literal operator template is:

template<char...> OutputType operator "" _tuffix();

A bit of practice

Let’s take the following example where we declare a buffer of 4 KB.

std::array<unsigned char, 4_KB> buffer;

This is identical to the following declaration (you’d usually expect)

std::array<unsigned char, 4096> buffer;

It is made possible by the existence of a literal operator with the following form:

constexpr size_t operator "" _KB(unsigned long long size)
{
   return static_cast<size_t>(size * 1024);
}

If the literal operator was not a constexpr then the compiler would trigger an error when declaring the buffer variable, because the size of the array must be known at compile time. You’d still be able to use the user-defined literal but in runtime contexts, such as sizing a vector.

std::vector<unsigned char> buffer(4_KB);

In the next example we define a user-defined literal for expressing temperatures in Fahrenheit degrees. Supposing the Celsius degrees are the norm, we can write sometime like this:

constexpr long double operator"" _fah(long double const degrees)
{
   return (degrees - 32.0) / 1.8;
}

and use it like in the following example:

auto threshold = 77.0_fah;
auto temp = 0.0;
std::cout << "Input temperature (in Celsius):";
std::cin >> temp;

std::cout << (temp < threshold ? "cold" : "warm") << std::endl;

The return type of the literal operator can be any type; it does not have to be a built-in type like in the previous examples. Given the following hierarchy of classes we can create user-defined literals that enable the creation of developer and quality assurer objects:

class Employee
{
   std::string name;
public:
   Employee(std::string const & name) :name(name){}
   std::string getName() const { return name; }
};

class Developer : public Employee
{
public:
   using Employee::Employee;
};

class QualityAssurer : public Employee
{
public:
   using Employee::Employee;
};

Developer operator "" _dev(char const * text, std::size_t const size)
{
   return Developer(std::string(text, text + size));
}

QualityAssurer operator "" _qa(char const * text, std::size_t const size)
{
   return QualityAssurer(std::string(text, text + size));
}

int main()
{
   auto d = "marius"_dev;
   auto q = "john"_qa;
   std::cout << d.getName() << std::endl;
   std::cout << q.getName() << std::endl;

   return 0;
}

In the next example we want to express latitudes, such as 66°33’39”N (the Arctic Circle). (Notice that the following types and just some simple implementations for demo purposes only).

class GeoCoordinate
{
protected:
   double degrees;
   double minutes;
   double seconds;
public:
   constexpr GeoCoordinate(double const degrees, double const minutes, double const seconds)
      :degrees(degrees), minutes(minutes), seconds(seconds)
   {}

   GeoCoordinate& operator+=(GeoCoordinate const & other)
   {
      seconds += other.seconds;
      minutes += other.minutes;
      degrees += other.degrees;

      return *this;
   }

   friend GeoCoordinate operator+(GeoCoordinate lhs, GeoCoordinate const & rhs)
   {
      return lhs += rhs;
   }

   double get_degrees() const { return degrees; }
   double get_minutes() const { return minutes; }
   double get_seconds() const { return seconds; }
};

enum class LatitudeDirection
{
   Undefined,
   North,
   South,
};

class Latitude : public GeoCoordinate
{
   LatitudeDirection direction;
public:
   constexpr Latitude(double const degree, double const minutes, double const seconds, LatitudeDirection const dir = LatitudeDirection::Undefined)
      :GeoCoordinate(degree, minutes, seconds), direction(dir)
   {}

   Latitude& operator+=(Latitude const & other)
   {
      if (direction == other.direction || direction == LatitudeDirection::Undefined || other.direction == LatitudeDirection::Undefined)
      {
         seconds += other.seconds;
         minutes += other.minutes;
         degrees += other.degrees;

         if (direction == LatitudeDirection::Undefined)
         {
            direction = other.direction;
         }
      }
      else
         throw std::exception("Invalid latitude direction");

      return *this;
   }

   friend Latitude operator+(Latitude lhs, Latitude const & rhs)
   {
      return lhs += rhs;
   }

   friend Latitude operator+(Latitude lhs, LatitudeDirection dir)
   {
      if (lhs.direction != LatitudeDirection::Undefined &&
         lhs.direction != dir)
         throw std::exception("Invalid latitude direction");

      Latitude l = lhs;
      l.direction = dir;
      return l;
   }
};

Latitude operator+(GeoCoordinate lhs, LatitudeDirection dir)
{
   return Latitude(lhs.get_degrees(), lhs.get_minutes(), lhs.get_seconds(), dir);
}

With this in place we can create objects like this:

auto g1 = GeoCoordinate(66, 0, 0);
auto g2 = GeoCoordinate(0, 33, 0);
auto g3 = GeoCoordinate(0, 0, 39);
auto g4 = g1 + g2 + g3;
auto g5 = GeoCoordinate(66, 0, 0) + GeoCoordinate(0, 33, 0) + GeoCoordinate(0, 0, 39);

auto l1 = Latitude(66, 0, 0, LatitudeDirection::North);
auto l2 = Latitude(0, 33, 0);
auto l3 = Latitude(0, 0, 39);
auto l4 = l1 + l2 + l3;

auto l5 = Latitude(66, 0, 0, LatitudeDirection::North) + Latitude(0, 33, 0) + Latitude(0, 0, 39);
auto l6 = Latitude(66, 0, 0) + Latitude(0, 33, 0) + Latitude(0, 0, 39) + LatitudeDirection::North;
auto l7 = GeoCoordinate(66, 0, 0) + GeoCoordinate(0, 33, 0) + GeoCoordinate(0, 0, 39) + LatitudeDirection::North;

Values like Latitude(66, 0, 0) are not very intuitive. Even though it’s more verbose it may be more desirable to be able to create objects like this:

auto lat = deg(66) + min(33) + sec(39) + LatitudeDirection::North;

That is possible if we define deg(), min() and sec() as following:

constexpr GeoCoordinate deg(double value)
{
   return GeoCoordinate(value, 0, 0);
}

constexpr GeoCoordinate min(double value)
{
   return GeoCoordinate(0, value, 0);
}

constexpr GeoCoordinate sec(double value)
{
   return GeoCoordinate(0, 0, value);
}

User-defined literals makes is more simple and more natural. By transforming the above functions into literal operators we can simplify the syntax.

constexpr GeoCoordinate operator "" _deg(long double value)
{
   return GeoCoordinate(value, 0, 0);
}

constexpr GeoCoordinate operator "" _min(long double value)
{
   return GeoCoordinate(0, value, 0);
}

constexpr GeoCoordinate operator "" _sec(long double value)
{
   return GeoCoordinate(0, 0, value);
}

As a result we can now create latitudes like this:

auto lat = 66.0_deg + 33.0_min + 39.0_sec + LatitudeDirection::North;

It should be very simple to develop this to support longitudes. You don’t have to add more literal operators, just the Longitude type and the appropriate overloaded operators for it.

Standard user-defined literals

C++14 defines several literal operators:

  • operator""if, operator""i and operator""il for creating a std::complex value
    #include <complex>
    
    int main()
    {
       using namespace std::literals::complex_literals;
       std::complex<double> c = 1.0 + 2i;
    }
    
  • operator""h, operator""min, operator""s, operator""ms, operator""us, operator""ns for creating a std::chrono::duration value
    #include <chrono>
    
    int main()
    {
       using namespace std::literals::chrono_literals;
       auto timer = 1h + 30min + 30s; // duration 01:30:30
    }

    This is equivalent to the following (longer) form in C++11:

    auto timer = std::chrono::hours(1) + std::chrono::minutes(30) + std::chrono::seconds(30);
    
  • operator""s for converting a character array literal to a std::basic_string
    int main()
    {
       using namespace std::string_literals;
       std::string s1 = "text\0";  // s1 = "text"
       std::string s2 = "text\0"s; // s2 = "text\0"
    }
    

Notice that all these literal operators are defined in separate namespaces that you have to use.

References

Compiler support

User defined literals are supported by major compilers starting with the following version:

See also:

3 thoughts on “User defined literals”

  1. Slight error in the first code snippet – “char[7]” should read “const char[7]” and “wchar_t[5]” should read “const wchar_t[5]”.

    Reply

Leave a Comment