Motivation
With C++ having gotten optional’s (in C++17), it is now common to want to write the equivalent of:
If a condition holds, initialize my variable with some expression; and if the condition doesn’t hold, keep my variable empty/unassigned
How would we write that in C++? Suppose our expression is of type int
. The “ideal”, or most convenient, syntax one might hope would work is:
auto myopt = some_condition ? std::nullopt : 123;
but optional is a library type, which the language knows nothing about, so it doesn’t have much of a reason to harmonize nullopt_t
with int
into an optional<int>
.
So, let’s try being a little more verbose:
std::optional<int> myopt = some_condition ? std::nullopt : 123;
This still fails, as int
can’t be converted into nullopt_t
and vice-versa.
We could write this:
auto myopt = some_condition ? std::nullopt : std::optional<int>(123);
which is ok-ish. But we’re again in trouble if our type is not a plain integer but something more complex:
struct mystruct { int x; double y; };
/// -snip-
auto myopt = some_condition ? std::nullopt : std::optional<mystruct>{ 12, 3.4 };
… which fails to compile.
so,
Actual question
What is a decent, succinct (DRY…) idiom for initializing an optional with either a value or nullopt? Note it must work for more complex types as well.
Here are three alternatives, from my least-favorite to my most-favorite:
- Use
in_place_t
- Use CTAD
- Use a utility function
Alternative 1: Use explicit emplacement-construction of std::optional
In your question, you’re trying to initialzie the optional<T>
with either a nullopt_t
or a T
pr-value. But – what about in-place construction of a T
within the optional? Indeed, that is possible, though not very pretty, by explicitly saying you want to do so. optional<T>
has the constructor:
template< class... Args >
constexpr explicit optional( std::in_place_t, Args&&... args );
and in your case, its use would mean writing:
auto myopt = some_condition ? std::nullopt : std::optional<mystruct>{ std::in_place_t{}, 12, 3.4 };
which compiles and does what you want. DRY is observed, sort of – but it ain’t pretty.
Alternative 2: Use deduction guides
Let’s try a minimum-change-from-your-code approach.
Fortunately, in C++17 we didn’t just get optional’s, we also got class template argument deduction (CTAD) guides, specifically for optional’s. So, this works:
auto myopt = some_condition ? std::nullopt : std::optional{ mystruct{ 12, 3.4 } };
and you’ve just replaced optional<T>{ ... }
{with
optional{ T{ … } }`. Not so bad.
Alternative 3: Write a function for it
and if you want to ensure terseness (whihle avoiding obfuscation), you could write:
template <typename T>
auto value_if(T&& t, bool cond) {
return cond ? std::nullopt : std::optional{ std::forward<T>(t) };
}
and then use it like so:
auto myopt = value_if(mystruct{12, 3.4}, some_condition);