Does C++ have some equivalent of SQL coalesce?

Viewed 57

In SQL, COALESCE(val_1, val_2, ... val_n) is a variadic function which returns its first non-null argument, or null otherwise.

Now, in C++, we have pointers which can be null, but also std::optional's since C++17, and there's std::variant which could be null or hold the monostate type.

Is there some standard library function in C++ which accepts multiple nullable (or emptyable) objects, and returns the first non-null? If not, has one been proposed, e.g. in the context of monadic programming perhaps?

1 Answers

Not aware of any, but you can easily write one:

template<typename T>
auto coalesce(T&& t) { return *t; }

template<typename T1, typename... Ts>
auto coalesce(T1&& t1, Ts&&... ts)
{
    if (t1)
    {
        return *t1;
    }
    return coalesce(std::forward<Ts>(ts)...);
}

You can also make it an operator (e.g. in a namespace, so you don't pollute global operators) or make it a custom operator.


The above solves the problem when you pass nullable types. Upon request, here's a version that works for the case when you'd like to mix nullable and non-nullables - for non-nullables, it'll return the first non-nullable, of course. This uses C++20:

template<typename T1, typename... Ts>
auto coalesce(T1&& t1, Ts&&... ts)
{
    if constexpr (requires { *t1; })
    {
        if constexpr (sizeof...(ts))
        {
            return coalesce(std::forward<Ts>(ts)...);
        }
        return *t1;
    }
    return std::forward<T1>(t1);
}

Note that even this version has the issue that you can only call it with one underlying type (as in, std::common_type<T1, Ts...> must exist). If you'd like to be able to process multiple (potentially) unrelated underlying types, you can resort to continuation passing:

template<typename Cont, typename T1, typename... Ts>
auto coalesce(const cont& c, T1&& t1, Ts&&... ts)
{
    if constexpr (requires { *t1; })
    {
        if constexpr (sizeof...(ts))
        {
            return coalesce(cont, std::forward<Ts>(ts)...);
        }
        return cont(*t1);
    }
    return cont(std::forward<T1>(t1));
}
Related