8

I'm writing some code using std::optional's and am wondering if C++17's 'if statements with initializers' will be able to help unpack values?

std::optional<int> optionalInt = GetOptionalInt();

I'm making up the function Unpack here:

if( auto [value, has_value] = optionalInt.Unpack(); has_value )
{
    // Use value here.
}

But, my question is. Will C++17 'if statement with initializer' help here? If so, how would it be coded?

Update, this is actually mainly an issue when using optional which is extremely easy to misuse because the optional and *optional both return bools and you don't get any compiler warning when somebody trys to access the value and forgets the *.

Eugene
  • 10,957
  • 20
  • 69
  • 97
Scott Langham
  • 58,735
  • 39
  • 131
  • 204

3 Answers3

7

There is not, and cannot possibly be, such an Unpack() function.

But you could certainly do:

if (std::optional<int> o = GetOptionalInt(); o) {
    // use *o here
}

though the extra o check is kind of redundant.


This is one of those places where it'd be nice if optional<T> modeled a container of at most one element, so that you could do:

for (int value : GetOptionalInt()) {
    // possibly not entered
}

but we don't have that interface.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • There couldn't be an Unpack, I guess because it would have to copy construct the value where really you'd want a reference to it, and the type would have to have a default constructor for when there is no value? – Scott Langham Sep 20 '16 at 16:38
  • 4
    I guess this already works now: if (std::optional o = GetOptionalInt()) { // use *o here } – Scott Langham Sep 20 '16 at 16:38
  • @ScottLangham Correct on both accounts. – Barry Sep 20 '16 at 16:41
  • @ScottLangham: Yes, because `optional` is contextually convertible to `bool`. For those situations the *condition* part of the existing control structures is already sufficient, because it be a declaration of something that's boolish. The initializer is useful for all the *other* cases, e.g. `if (auto it = m.find(key); it != m.end())`. In that case just `if (auto it = m.find())` doesn't work, because an iterator alone doesn't form a predicate. – Kerrek SB Sep 30 '16 at 09:08
  • @KerrekSB. Yes. In the case where are working with std::optional this all becomes very error prone though. It's very easy to forget the * in the body of the if. So some way of unwrapping the value and testing it is valid at the same time could be useful to avoid that mistake. I guess Barry's for(int value : GetOptionalInt()) might be a solution, some adapter could make it work: for(int value: OptionalToZeroOrOneElementContainer(GetOptionalInt()). A snappier name might be required! – Scott Langham Sep 30 '16 at 10:53
  • @ScottLangham: For an optional bool, you could handle all cases: `if (optional ob = f(); !o) { ... } else if (!*o) { ... } else { ... }`. – Kerrek SB Sep 30 '16 at 11:33
3

In order for this to work, there has to be a value for the unpacked value if it isn't there.

So

template<class T, class U>
std::pair< T, bool > unpack_value( std::optional<T> const& o, U&& u ) {
  return { o.value_or(std::forward<U>(u)), (bool)o } )
}

would do what you wanted.

But as an optional already returns if it is engaged in a bool context you really should just:

if (auto i = get_optional())

then use *i within the body.

...

Now if optional stated that operator* returned a reference, and that return value was defined but accessing it was not defined when it was not engaged, then you could write an Unpack method or function that doesn't require a default value.

As far as I am aware this is not true. And as it doesn't really add anything, I don't see why it should be true.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
-1

Maybe this would work:

auto optValue = getOptional();
if (auto value = *optValue; optValue) { ...use value here... }
Cthutu
  • 8,713
  • 7
  • 33
  • 49