Expression checking at compile time - Modern C++

Today, you have to write a C++ template function that does something based on the capabilities of the type it is instanciated with. Let’s consider the following example:

/// Prints the given obj to std::cout.
/// If available, uses operator<<(ostream&, const T&).
/// Otherwise tries obj.to_string() and to_string(obj). 
/// Results in a compile-time error if none of the above is available.
template<typename T> void print(const T& obj);

In some other languages this could be done with reflection or something similar but in C++ we have something more poweful: template meta programming. But how to actually implement it? We want to switch the implementation based on the functionality the type offers. Although the given example is probably not the most useful one, this is a common scenario.

Trying to find an implementation using the usual template specialization mechanisms I came to the conclusion that there is no possibility to get this right without any pain. There is no way to not make this really ugly, error-prone and hard to read as well as using a reasonable number of lines (at least this is my conclusion, if you find a good solution and want to make me feel bad about writing bad introductions, tell me!). The task isn’t that hard in the end is it?

Combining C++17’s new constexpr if with the beauty of std::declval and decltype, while also adding two (upon closer inspection really insignificant) little bits of magic named validExpression<Expr, Args...> and templatize<T> to the mix, we can achieve this in a really compact and readable manner:

// Expressions to test validity wrapped into typedefs using decltype
template<typename T> using Ostream = decltype(std::cout << std::declval<T>());
template<typename T> using Member = decltype(std::cout << std::declval<T>().to_string());
template<typename T> using Free = decltype(std::cout << to_string(std::declval<T>()));

// The implementation
template<typename T>
void print(const T& obj) {
    if constexpr(validExpression<Ostream, T>)
        std::cout << obj;
    else if constexpr(validExpression<Member, T>)
        std::cout << obj.to_string();
    else if constexpr(validExpression<Free, T>)
        std::cout << to_string(obj);
    else static_assert(templatize<T>(false), "T is not printable!");
}

The implementation above works exactly as specified, but implements everything explicitly in one function, specifies an order in which possible candidates may be chosen and outputs a custom error message at compile-time using static_assert when nothing works. As mentiod above, to achieve exactly this with template specialization would result in various problems. The declarative style of template specializations makes it hard to handle corner cases, like e.g. the type has an operator<< overload and a to_string() member.

This way of switching between the possible implementation candidates can be read and written really naturally like normal branching - just at compile time. On the downside though, it is not extendable from the ouside like template specialization would be but in many cases this is not needed anyways (or maybe explicitly not wanted).

validExpression<Expr, Args...>

As promised, let's inspect validExpression<Expr, Args...> more closely. To be honest, there is absolutely nothing magical about it, it just uses the default SFINAE construct to check if the given type(-expression) is valid. But using it amazes me everytime, since it strips away the need to write all the boring boiler plate and instead lets us test super easily at compile time if a given expression (or in this case: typedef, but this can be used to check for expressions via decltype) is valid. This shows again the true power of template meta programming in C++. Is there any other major language that allows this (I don't mean simply running a parser at runtime)?

I actually feel a bit ashamed that this post is mainly about 10 lines of code:

// Not-so-important helpers
namespace detail {
    template<template<class...> typename E, typename C, typename... T>
    struct ValidExpressionT
        : public std::false_type {};

    template<template<class...> typename E, typename... T>
    struct ValidExpressionT<E, std::void_t<E<T...>>, T...>
        : public std::true_type {};
}

// The final templated boolean
template<template<typename...> typename Expr, typename... Args>
constexpr auto validExpression = detail::ValidExpressionT<Expr, void, Args...>::value;

So Expr is a templated typedef that will be instanciated with the given variadic Args.... The validExpression value will be true if Expr with Args... can be evaluated or false if not. Of course this cannot be used to detect syntax-errors or check for non-templated expressions like foo(5); // valid call?, but these are different cases and not nearly as interesting. We don’t actually know at the time of writing the code which expressions for a given template type are valid and which are not, while we (at least theoretically) should know it for non-templated functions and objects.

The templatize<T> utility used is even more mundane, but can still be really useful at times:

template<typename T, typename O>
constexpr decltype(auto) templatize(O&& value)
    { return std::forward<O>(value); }

Really? A function that just returns its argument and takes an additional, useless template parameter? Yep.
This makes sure the given value is evaluated only when the template is instanciated, i.e. postponing it to the second phase of two-phase-lookup. In this case it makes sure that the static_assert only really fails when our constexpr branches reach the last case. There are other hacks (stuff I probably don’t want to see anywhere as well as maybe interesting use cases) that can be done with this utility, it is all about changing the time at which the compiler is required or able to evaluate expressions.

Meta-programming unit testing

Is this actually a thing? Have you ever written unit tests that run fully at compile time? When writing unit tests on some small linear algebra templates in C++ I thought about how to assure that expressions don’t compile. The same way I want that Vec3f{} + Vec3f{} compiles and has the correct semantics, I want to make sure Vec3f{} + Mat4f{} does never compile, since that would not make any sense (but happens faster than think when working with template operators).

With the little magic above this is super easy:

template<typename A, typename B> using Add = decltype(std::declval<A>() + std::declval<B>());

static_assert(validExpression<Add, Vec3f, Vec3f>);
static_assert(!validExpression<Add, Vec3f, Mat4f>);

That's it. The unit test will fail at compile time if someone thinks it would be a good idea to implement operator+ for vector and matrix or gets so tangled up in some templated operators that this would actually compile to some weird bullshit.

Edit: As a reddit user has pointed out, this is actually already part of the experimental standard library and known as the detection idiom so we might see it in the next C++ standard (in a few years).


Tuesday, 16.05.2017, by nyorain



Just started to explain things to people they already know or wasting their time with boring stuff they don’t want to know, so I really appreciate all feedback (and discussion). If you spent your last 5 or 10 minutes reading something extremely stupid with lots of spleling mistakes or wasting your time with something absolutely mundane, waiting for this idiot to finally come to and end… – make the time worth it and let me know: nyorain@gmail.com

The code (for validExpression as well as the linear algebra example) is actually taken from https://github.com/nyorain/nytl

"Oh god, just another blog post about programming that uses a totally unrelated and not even beautiful picture in its header. Booooring!" Actually, the picture is beautiful and the second result when searching for meta on pixabay.