Welcome to the next pikoTutorial!
What are C++ literals?
Literals in C++ are constant values directly embedded into the code, such as numbers, characters and strings. They represent fundamental data, making the code more readable and expressive. C++ provides several built-in literals, like integer literals (42), floating-point literals (3.14), and string literals (“Hello”). However, C++ also allows developers to define custom literals, enabling more readable and type-safe expressions tailored to specific needs.
For example, consider the use of the standard chrono literals for handling time durations:
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
int main() {
auto duration = 500ms;
std::this_thread::sleep_for(duration);
std::cout << "Waiting for " << duration.count() << "ms done!";
}
Here, the 500ms literal is much more readable than simply writing 500, as it explicitly conveys that the value represents milliseconds. This type-safe approach prevents errors by associating units directly with values, making the code both easier to understand and less prone to mistakes.
But that’s not all – because the duration value is not just a number, but a dedicated type std::chrono::milliseconds
, we can overload a stream operator for this specific type. The benefit of such approach is that user no longer needs to call duration.count()
and add string “ms” after during the printing. Variable duration
can be used directly:
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
std::ostream& operator<<(std::ostream& stream, std::chrono::milliseconds value)
{
return stream << value.count() << "ms";
}
int main() {
auto duration = 500ms;
std::this_thread::sleep_for(duration);
std::cout << "Waiting for " << duration << " done!";
}
This produces the following output:
Waiting for 500ms done!
Defining custom literals
Standard C++ literals are useful, but you may want to define your own, custom ones. Custom literals are defined using operator overloading combined with a literal, which is typically preceded by an underscore:
ReturnType operator "" _literal(ParameterType parameter);
Although the underscore prefix is not mandatory, it is highly recommended. Names without an underscore will generate a compiler warning that such identifiers are reserved for future standardization. The underscore ensures that your custom literal does not conflict with existing or future literals in the standard library.
Example of custom literal usage
Below you can find an example showing how to define your custom literal dedicated for byte strings which are supposed to be stored in a vector of bytes.
#include <iostream>
#include <vector>
// define custom literal
std::vector<uint8_t> operator "" _B(const char* input, size_t) {
std::vector<uint8_t> bytes;
for (size_t i = 0; input[i] && input[i + 1]; i += 2) {
std::string byte_string(input + i, 2);
bytes.push_back(static_cast<uint8_t>(std::stoi(byte_string, nullptr, 16)));
}
return bytes;
}
int main() {
// use custom literal
auto data = "A1B2C3D4"_B;
// print converted data
for (auto byte : data) {
std::cout << std::hex << static_cast<int>(byte) << ' ';
}
}
When you run this code, you will see the following output:
a1 b2 c3 d4