Is there an idiom like `if (Value * value = getValue())` when you branch on an expression of the retrieved value?

610 views Asked by At

I am often using the common

if (Value * value = getValue())
{
    // do something with value
}
else
{
    // handle lack of value
}

Now, I also often do

QString error = someFunctionReturningAnErrorString(arg);
if (!error.isEmpty())
{
     // handle the error
}
// empty error means: no error

That's all fine but I would like the error variable to be scoped to the if-block. Is there a nice idiom for that? Obviously, I can just wrap the whole part inside another block.

This, obviously, does not work:

if(QString error = someFunctionReturningAnErrorString(arg), !error.isEmpty())
{
    // handle the error
}
// empty error means: no error

And unfortunately (but for good reasons) the QString cannot be converted to bool, so this does not work either:

if(QString error = someFunctionReturningAnErrorString(arg))
{
    // handle the error
}
// empty error means: no error

Any suggestions?

6

There are 6 answers

2
Barry On BEST ANSWER

The only way to use that idiom while still keeping your code understandable is if your function returns an object that is convertible to bool in a way that true indicates that you want to take the branch and false means that you do not care about it. Anything else is just going to lead to write-only code.

One such object which may be relevant happens to be boost::optional. Given:

boost::optional<QString> someFunctionReturningAnErrorString(T arg);

You could use the idiom you want in a natural way:

if (auto error = someFunctionReturningAnErrorString(arg)) {
    // ...
}

This also has the added benefit where I'd consider an optional error message more semantically meaningful than having to check for an empty error message.

1
user253751 On

You could use:

for(QString error = someFunctionReturningAnErrorString(arg); !error.isEmpty(); /* too bad 'break' is invalid here */)
{
    // handle the error
    break;
}

but this is ugly, and makes your code hard to read. So please don't.

1
jxh On

You can use a lambda.

auto error_string_handler = [](QString && error) {
    if (error.isEmpty()) return;
    //...
}

error_string_handler(someFunctionReturningAnErrorString(arg));
1
Yakk - Adam Nevraumont On
if(auto message = maybe_filter( getError(arg), [](auto&&str){
  return !str.isEmpty();
}) {
}

where maybe_filter takes a T and a test function and returns optional<T>. The optional<T> is empty if evalutating the test function on the T gives you false, and T otherwise.

Or really, modify your error getting API to return an optional string.

0
Lightness Races in Orbit On

No. There is no idiom like this, and there is no syntax like this!

Besides, you have reached the point at which it is no longer worthwhile to make your code more and more obfuscated.

Simply write it as you do now.

If you really don't want the scope leakage, introduce a new scope:

{
   const QString error = someFunctionReturningAnErrorString(arg);
   if (!error.isEmpty()) {
      // handle the error
   }
}
// The above-declared `error` doesn't exist down here

I use this pattern quite a lot, though I've been fairly accused of scope-addiction, so take that as you will.

3
tux3 On

There is basically no clean way to do that.

I'd recommend you just define an extra block around the if, but if you really want to have that exact syntax, a solution could be to declare your own class wrapping QString:

struct ErrorString
{
    ErrorString(QString&& s) : s{move(s)} {}
    operator bool() {return !s.isEmpty();}

    QString s;
};

And then you could write:

if(ErrorString error = someFunctionReturningAnErrorString(arg))
{
    // handle the error
}
// empty error means: no error

But I'm not particularly fond of this solution.