You might use
template <typename T, typename F> auto convert_optional(const std::optional<T>& o, F&& f) -> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *o))>> { if (o) return std::invoke(std::forward<F>(f), *o); else return std::nullopt; } template <typename T, typename F> auto convert_optional(std::optional<T>& o, F&& f) -> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *o))>> { if (o) return std::invoke(std::forward<F>(f), *o); else return std::nullopt; } template <typename T, typename F> auto convert_optional(std::optional<T>&& o, F&& f) -> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *std::move(o)))>> { if (o) return std::invoke(std::forward<F>(f), *std::move(o)); else return std::nullopt; }
or
template <typename> struct is_optional : std::false_type {}; template <typename T> struct is_optional<std::optional<T>> : std::true_type {}; template <typename O, typename F> auto convert_optional(O&& o, F&& f) -> std::enable_if_t< is_optional<std::decay_t<O>>::value, std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *std::forward<O>(o)))>>> { if (o) return std::invoke(std::forward<F>(f), *o); else return std::nullopt; }
and your example becomes:
auto c = convert_optional(convert_optional(a, &A::b).value_or(std::nullopt), &B::c).value_or(std::nullopt);
convert_optional(a, &A::b) will return std::optional<std::optional<B>>
You might even simplify by additional function:
template <typename O, typename F> auto convert_optional_fact(O&& o, F&& f) -> decltype(convert_optional(std::forward<O>(o), std::forward<F>(f)).value_or(std::nullopt)) { return convert_optional(std::forward<O>(o), std::forward<F>(f)).value_or(std::nullopt); }
and then
auto c = convert_optional_fact(convert_optional_fact(a, &A::b), &B::c);
Demo
std::optionalandc++11doesn't sound right to me.if (a->b && a->b->c) { const auto c = *a->b->c; // do stuff with c }