Chris Beck
Chris Beck

Reputation: 16204

How to combine negation with declaration inside if-statement?

When using C-style return codes to signal errors, it's pretty common to see code like this:

if (do_something()) {
  do_something_else();
} else {
  report_failure();
}

Sometimes, if one block is much larger than the other, you might want to reorder the "handle failure" block before the "do_something_else" block.

if (!do_something()) {
  report_failure();
} else {
  do_something_else();
}

(Or, when it really is C-code the codes may be such that 0 indicates success rather than failure, but let's ignore that.)

When I use C++ idioms like boost::optional or one of the proposed std::expected types, usually what I want to do is put a declaration inside the condition of the if statement:

if (auto ok = do_something()) {
  do_something_else(*ok);
} else {
  report_failure(ok.error());
}

I like to do this because this way, ok is strictly contained in scope, it's not visible outside the two blocks.

However, once I do it this way, I can't reorder the two branches if I want, which probably annoys me much more than it should, but still.

What I would really like is a syntax

if not (auto ok = do_something()) {
  report_failure(ok.error());
} else {
  do_something_else(*ok);
}

But to my knowledge that doesn't actually work.

Is there a trick to accomplish that?

Upvotes: 3

Views: 109

Answers (4)

srdjan.veljkovic
srdjan.veljkovic

Reputation: 2548

Well, a lot of dirty tricks come to mind involving macros, but, supposing you don't want to go there, here's a non-macro trick:

template <class T> class notter {
    T d_t;
public:
    notter(T &t) : d_t(t) {}
    notter(T t) : d_t(t) {}
    operator bool() { return !d_t; }
    T &data() { return d_t; }
};

Now you can use it as:

if (notter<int> a = do_something()) {
    report_failure();
}
else {
    do_something_else(a.data());
}

This assumes that do_something returns an int. You may avoid naming the type with decltype like this:

if (notter<decltype(do_something())> a = do_something()) {

but in cases like this, that may be overkill.

You may tweak it to your needs, if, say, data() is too verbose for you, or you want just one of the constructors, or to make a more "drop-in replacement" for optional<> (as per comments from Duthomhas) or expected<> - you may employ template specialization.

Also, you can take hint from std::make_shared() and such:

template<class T> notter<T> make_notter(T t) { return notter<T>(t); }

and use it like:

if (auto a = make_notter(do_something())) {

Upvotes: 0

D&#250;thomhas
D&#250;thomhas

Reputation: 10048

Alright, so here's a little class that does what you want. Dress it up however you like.

template <typename T>
struct not_optional_type: public optional <T>
{
  typedef optional <T> base_type;
  not_optional_type( const base_type& v ): base_type( v ) { }
  operator bool () const { return !(base_type)(*this); }
  T operator * () const { return *(base_type)(*this); }
};

template <typename T>
not_optional_type <T>
not_optional( const optional <T> && v )
{
  return not_optional_type <T> ( v );
}

Use it as you would expect:

if (auto ok = not_optional( do_something() ))
  fooey();
else
  success( *ok );

I personally think the proposed if syntax modification is an abomination.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

C++17 will introduce this syntax:

if (auto ok = do_something(); !ok) {
  report_failure(ok.error());
} else {
  do_something_else(*ok);
}

Which is basically what you want.

It is in the feature-complete draft.

Upvotes: 2

Karoly Horvath
Karoly Horvath

Reputation: 96258

You can add an extra scope:

{
    auto ok = do_something();
    if (! ok) {
      report_failure(ok.error());
    } else {
      do_something_else(*ok);
    }
}

Personally I wouldn't add those braces as the scope should be clear from the rest of the code, if you have too much functionality in one function you should refactor the code anyways...

Upvotes: 2

Related Questions