Alright, I messed up asking this question the first time.
Is there a way, idiomatically, to provide a constructor which takes one or more std::optional<T> and returns a std::optional<U>? For instance, ideally I would love some kind of syntax like
#include <optional>
struct Rational {
explicit Rational(int i) : num{i}, denom{1} {}
Rational(int i, int j) : num{i}, denom{j} {}
// magic here?...
int num, denom;
};
int main()
{
Rational i1 = Rational(1); // the number 1
Rational h2 = Rational(1,2); // the number 1/2
std::optional<int> opt_i{}, opt_j{};
std::optional<Rational> e3 = std::optional<Rational>(opt_i);
// empty, converting copy constructor
/* std::optional<Rational> e4 = std::optional<Rational>(opt_i, 2); */
/* std::optional<Rational> e5 = std::optional<Rational>(2, opt_i); */
/* std::optional<Rational> e6 = std::optional<Rational>(opt_i, opt_j); */
// error, no constructor
// ideally would construct empty std::optional<Rational>
opt_i = 3;
std::optional<Rational> i7 = std::optional<Rational>(opt_i);
// contains Rational(3), the number 3
/* std::optional<Rational> r8 = std::optional<Rational>(opt_i, 2); */
/* std::optional<Rational> r9 = std::optional<Rational>(2, opt_i); */
// error, no constructor
// ideally would construct std::optional<Rational> containing 3/2 and 2/3, respectively
opt_j = 4;
/* std::optional<Rational> r10 = std::optional<Rational>(opt_i, opt_j); */
// error, no constructor
// ideally would construct std::optional<Rational> containing 3/4
}
Thanks to 463035818_is_not_an_ai's answer on the first question, we know that for simple converting/copy constructors std::optional provides an overload for its constructor. For general constructors, I believe this is not currently possible. What are my best options?
With C++17, mostly yes. But there are a few caveats. Here's the secret sauce.
We could have written this as a single function taking
template <typename C, typename... Ts>, but then you'd always have to specify your template arguments if you wanted to specifyC, which isn't really what you want. So by splitting it into a constructor (whose type takesC) and a call operator (which will inferTs...), we can omit theTs...part in most cases.So what does this do? C++17 introduced a neat little feature called fold expressions, which is what gives us that nice short
ifstatement.This says "if all of the arguments are true in Boolean context". In that case, we forward all of our arguments, dereferenced (which we now know is safe), to the constructor for
C. If not, we returnnullopt, the empty optional.Example usage:
Now the caveat is that this only works if you're passing all
std::optionalvalues. Some of your examples involve mixed types, which won't work.C++ won't try to do the implicit conversion from
inttostd::optional<int>for the second argument since it won't infer the template argument in that case. We can provide explicit template arguments, but it's not pretty.Now C++ knows that the second argument must be
std::optional<int>for certain, so it's happy to do the implicit conversion. We can clean that up a little bit if we define acreatemember function that delegates tooperator()Then we can write
which is, at least, a little better.
By the way, while this won't help you in C++ specifically, there is a word for what you're trying to do. You're taking an applicative functor (which
std::optionalis an excellent example of) and a function that doesn't use the applicative functor, and you're lifting the argument and result types into the applicative functor. These are called applicative brackets, or sometimes idiom brackets.