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.
3 Answers 3
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;
}
-
1\$\begingroup\$ It's too bad we can't define templated classes within functions. \$\endgroup\$einpoklum– einpoklum2019年12月21日 21:16:01 +00:00Commented Dec 21, 2019 at 21:16
-
\$\begingroup\$ @einpoklum-reinstateMonica There is no problem an additional level of indirection cannot solve. \$\endgroup\$Deduplicator– Deduplicator2019年12月21日 22:16:26 +00:00Commented Dec 21, 2019 at 22:16
-
\$\begingroup\$ @einpoklum-reinstateMonica we can't? \$\endgroup\$Noone AtAll– Noone AtAll2019年12月22日 06:08:45 +00:00Commented Dec 22, 2019 at 6:08
-
\$\begingroup\$ @NooneAtAll: Lambdas excluded from that statement obviously, but - no, we can't. \$\endgroup\$einpoklum– einpoklum2019年12月22日 12:01:57 +00:00Commented Dec 22, 2019 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 (sinceauto
doesn't deduce references). This can easily be fixed by changing the return type ofcoalesce
toauto&
(preferred in this case) ordecltype(auto)
. \$\endgroup\$hoffmale– hoffmale2019年12月22日 15:52:36 +00:00Commented Dec 22, 2019 at 15:52
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).
-
\$\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– Deduplicator2019年12月21日 20:50:45 +00:00Commented Dec 21, 2019 at 20:50
-
\$\begingroup\$ You can use the trick of @Deduplicator and write
using T = std::common_type_t<Ts...>
, so you can dropclass T
from the template arguments, and then useauto
return type. \$\endgroup\$G. Sliepen– G. Sliepen2019年12月21日 21:08:23 +00:00Commented Dec 21, 2019 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– einpoklum2019年12月21日 21:11:42 +00:00Commented Dec 21, 2019 at 21:11 -
1\$\begingroup\$ I haven't seen a real programmers statement in decades! Thank you. \$\endgroup\$2019年12月22日 11:24:37 +00:00Commented Dec 22, 2019 at 11:24
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 {};
}
-
\$\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– einpoklum2019年12月21日 21:13:13 +00:00Commented Dec 21, 2019 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– G. Sliepen2019年12月21日 21:28:44 +00:00Commented Dec 21, 2019 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– einpoklum2019年12月21日 21:35:39 +00:00Commented Dec 21, 2019 at 21:35 -
1\$\begingroup\$ Only if
coalesce()
should return a reference doesr
have to be a reference wrapper. \$\endgroup\$G. Sliepen– G. Sliepen2019年12月21日 21:41:55 +00:00Commented Dec 21, 2019 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– Deduplicator2019年12月21日 22:28:19 +00:00Commented Dec 21, 2019 at 22:28