Document number DXXXXRX
Date 2019-XX-XX
Reply-to Vittorio Romeo <>
Audience Evolution Working Group Incubator (EWGI)
Project ISO JTC1/SC22/WG21: Programming Language C++

Interpolated literals

Abstract

This paper proposes the addition of interpolated literals to the C++ language, a new form of string literals that can contain and retain arbitrary expressions.

Overview

One of the most common operations in any C++ code base is printing the value of a variable or the result of an expression alongside a human-readable message. Older techniques to achieve that goal, such as <iostream> and printf, have major drawbacks including verbosity and lack of safety. The upcoming <format> header addresses some of those problems, but still requires the user to pass the expressions that need to be printed separately from the rest of the message, and the formatting happens at run-time rather than compile-time.

This paper proposes a flexible language feature that does not intend to compete with <format> - instead, it intends to complete the set of C++ formatting tools available for users by providing a compile-time-friendly mechanism to process expressions embedded in string literal.

Execution i18n Expressions
<format> Run-time Supported Passed as arguments
Interpolated literals Compile-time Not supported Embedded in literal

Motivating example

Here’s how interpolated literals can be used to print out the result of an expression to stdout:

int get_result() { return 42; }
std::cout << f"The result is {get_result()}\n";

An interpolated literal always begins with the token f. Expressions can be embedded inside the literal by surrounding them with curly braces. Curly braces themselves can be escaped by doubling them.

The f"The result is {get_result()}\n" expression generates an anonymous type that satisfies the InterpolatedLiteral concept. This type “captures” all the embedded expressions by reference, and provides a visitation interface to walk over the elements of the literal. E.g.

// The type of the following expression...
f"The result is {get_result()}\n"

// ...is roughly equivalent to:
struct __anonymous_interpolated_literal_00
{
    const int& _get_result;

    constexpr __anonymous_interpolated_literal_00(const int& get_result) noexcept
        : _get_result{get_result}
    {
    }

    template <typename F>
    constexpr void operator()(F&& f) const noexcept(/* ... */)
    {
        f(literal_tag{}, "The result is ");
        f(expression_tag{}, _get_result);
        f(literal_tag{}, "\n");
    }
};

An overload of operator<< for std::ostream& can then be defined to work with any InterpolatedLiteral:

std::ostream& operator<<(std::ostream& os, const InterpolatedLiteral auto& il)
{
    il([&](auto /* tag */, const auto& x){ os << x; });
    return os;
}

A similar operator overload can be added for std::string:

std::string& operator+=(std::string& s, const InterpolatedLiteral auto& il)
{
    il(overload([&s](literal_tag, const char* literal){ s += literal; },
                [&s](expression_tag, const auto& expr){ s += std::to_string(expr); }));

    return s;
}

Complete flexibility is given to the consumer of InterpolatedLiteral instances thanks to the visitor approach. All the information regarding the literal and its elements is available at compile-time.

Other random examples of valid interpolated literal uses:

if (const auto [k, v] item = get_item(); !item.is_valid())
{
    log::error(f"The item with key {k} and value {v} is not valid.");
    std::terminate();
}
void debug_print([[maybe_unused]] const InterpolatedLiteral auto& il)
{
#ifndef NDEBUG
    std::cerr << il;
#endif
}

[[nodiscard]] long factorial(long n)
{
    debug_print(f"factorial({n})")

    if (n < 1) { return 1; }
    return n * factorial(n - 1);
}
struct pretty_cout { /* ... */ };

pretty_cout& operator+=(pretty_cout& os, const InterpolatedLiteral auto& il)
{
    il(overload([&os](literal_tag, const char* literal){ os << literal; },
                [&os](expression_tag, const auto& expr)
                {
                    os << ansi_color::green << expr << ansi_color::white;
                }));

    return s;
}

constexpr std::string_view name{"Bob"};
pretty_cout{} << f"Hello world! My name is {name}!\n";
    // "Bob" gets automatically colored in green in the console output.

TODO

So far, this proposal is just a draft to see if people like the approach. If there is enough positive feedback, these are some points to think about: