7
\$\begingroup\$

I want to coalesce (lvalue references to) optionals of the same type, in C++17:

#include <optional>

template <typename... Ts>
constexpr auto coalesce(std::optional<Ts>&... optionals );

template <typename T>
constexpr std::optional<T>& coalesce(std::optional<T>& x, std::optional<T>& y)
{
    return x.has_value() ? x : y;
}

template <typename T>
constexpr std::optional<T> coalesce(std::optional<T>& x)
{
    return x;
}

template <typename T, typename... Ts>
constexpr std::optional<T> coalesce(std::optional<T>& x, std::optional<Ts>&... xs )
{
    return coalesce(x, coalesce(std::forward<std::optional<T>&>(xs)...));
}

This is sub-optimal, as it uses recursive templated function calls, rather than ellipsis-based expansion in a single body. What would you suggest?

Notes:

  • C++17 and no fancy libraries please.
  • This can be seen as a specific case of the "return first parameter in a pack satisfying a predicate", with the predicate being [](auto x) { return x.has_value(); }
  • If possible, let's not copy any T's.
\$\endgroup\$
9
\$\begingroup\$

So ideally you want to write something like:

template <typename... Ts>
constepxr auto coalesce(std::optional<Ts>&... xs)
{
   return (xs || ...);
}

But the issue here is of course that it will convert the optionals to bool before applying the boolean or-operator. You could however cast it to a type that does provide the right semantics for the boolean or-operator to do the thing you want:

template <typename T>
struct coalescable {
    T &x;
    coalescable(T &x_): x(x_) {}
    constexpr const coalescable &operator||(const coalescable &other) {
        return x.has_value() ? *this : other;
    }
};

And then use that in the fold expression:

template <typename... Ts>
constepxr auto coalesce(std::optional<Ts>&... xs)
{
   return (coalescable(xs) || ...).x;
}
| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ It's too bad we can't define templated classes within functions. \$\endgroup\$ – einpoklum Dec 21 '19 at 21:16
  • \$\begingroup\$ @einpoklum-reinstateMonica There is no problem an additional level of indirection cannot solve. \$\endgroup\$ – Deduplicator Dec 21 '19 at 22:16
  • \$\begingroup\$ @einpoklum-reinstateMonica we can't? \$\endgroup\$ – Noone AtAll Dec 22 '19 at 6:08
  • \$\begingroup\$ @NooneAtAll: Lambdas excluded from that statement obviously, but - no, we can't. \$\endgroup\$ – einpoklum Dec 22 '19 at 12:01
  • 1
    \$\begingroup\$ Nice solution! Just a small nitpick: The original code returned std::optional<T>& (note the reference!), but your final example returns the optionals by value (since auto doesn't deduce references). This can easily be fixed by changing the return type of coalesce to auto& (preferred in this case) or decltype(auto). \$\endgroup\$ – hoffmale Dec 22 '19 at 15:52
9
\$\begingroup\$

Folding? Bah. New-fangled nonsense. Real programmers use for loops for for looping.

#include <optional>
#include <functional>

template<class T, class... Ts>
std::optional<T> coalesce(std::optional<Ts>&... xs)
{
    for (auto const& x : { std::reference_wrapper<std::optional<T>>(xs)... })
        if (x.get().has_value())
            return x;

    return {};
}

(Yeah, G. Sliepen's is better, and I still don't understand that other one).

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Upvote for making me laugh with "real programmers". There is just one fly in the ointment: You require a manually specified return-type. \$\endgroup\$ – Deduplicator Dec 21 '19 at 20:50
  • \$\begingroup\$ You can use the trick of @Deduplicator and write using T = std::common_type_t<Ts...>, so you can drop class T from the template arguments, and then use auto return type. \$\endgroup\$ – G. Sliepen Dec 21 '19 at 21:08
  • \$\begingroup\$ So, in what ways (other than elegance) is @G.Sliepen's answer better? IIANM yours would work with C++14 and and std::experimental::optional, wouldn't it? \$\endgroup\$ – einpoklum Dec 21 '19 at 21:11
  • \$\begingroup\$ I haven't seen a real programmers statement in decades! Thank you. \$\endgroup\$ – pacmaninbw Dec 22 '19 at 11:24
5
\$\begingroup\$

Well, if you don't want recursion, you have to fold instead.

template <class... Ts>
constexpr auto coalesce(std::optional<Ts>&... xs) {
    std::optional<std::common_type_t<Ts...>> r;
    ((xs && (r = xs, true)) || ...);
    return r;
}

If you want to avoid even the single copy to the result, you need to change the result-type by using an optional std::reference_wrapper or going with a potentially null pointer.

template <class... Ts>
constexpr auto coalesce(std::optional<Ts>&... xs) noexcept {
    std::common_type_t<Ts...>* r = nullptr;
    ((xs && (r = &*xs, true)) || ...);
    if (r)
        return std::optional(std::ref(*r));
    return {};
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Aren't you copying T's here? I didn't forbid it but it seems like it's better to avoid that. \$\endgroup\$ – einpoklum Dec 21 '19 at 21:13
  • \$\begingroup\$ I was a bit confused about this at first too. But r is only assigned to once, and return value optimization avoids it from being a temporary copy. Of course it still returns a copy of one of the inputs, but so did your original code. I think all answers here could be modified to return a reference to one of the inputs, if that is what you want. \$\endgroup\$ – G. Sliepen Dec 21 '19 at 21:28
  • \$\begingroup\$ @G.Sliepen: That's actually a bug in my code! I should have consistently returned references. Also, regardless - shouldn't r here be made a reference wrapper? \$\endgroup\$ – einpoklum Dec 21 '19 at 21:35
  • 1
    \$\begingroup\$ Only if coalesce() should return a reference does r have to be a reference wrapper. \$\endgroup\$ – G. Sliepen Dec 21 '19 at 21:41
  • \$\begingroup\$ @einpoklum-reinstateMonica If you really want to avoid even the copy to return-value, that needs std::reference_wrapper or returning a pointer. Your choice, added such an alternative. \$\endgroup\$ – Deduplicator Dec 21 '19 at 22:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.