How to deal with declaration of the primitive type without the initial value known (C++)?

Viewed 164

In some cases it happens for me to declare a variable without knowing its value first like:

int a;
if (c1) {
  a = 1;
} else if (c2) {
  a = 2;
} else if (c3) {
  a = -3;
} 
do_something_with(a);

Is it the standard professional practice to assign some clearly wrong value like -1000 anyway (making potential bugs more reproducible) or it is preferred not to add the code that does nothing useful as long as there are no bugs? From one side, looks reasonable to remove randomness, from the other side, magical and even "clearly wrong" numbers somehow do not look attractive.

In many cases it is possible to declare when the value is first known, or use a ternary operator, but here we would need it nested so also rather clumsy.

Declaring inside the block would move the variable out of the scope prematurely.

Or would this case justify the usage of std::optional<int> a and assert(a) later, making sure we have the value?

EDIT: The bugs I am talking about would occur if suddenly all 3 conditions are false that should "absolutely never happen".

5 Answers

As far as I know the most popular and safest way is using inline lambda call. Note that the if should be exhaustive (I added SOME_DEFAULT_VALUE as a placeholder). I suppose that if you don't know what to put in final else block you should consider a few options:

  • using optional and putting none in the else,
  • throwing exception that describes the problem,
  • putting assert if logically this situation should never happen
const int a = [&] {
  if (c1) {
    return 1;
  } else if (c2) {
    return 2;
  } else if (c3) {
    return -3;
  } else {
    return SOME_DEFAULT_VALUE;
  }
}();
do_something_with(a);

In a situation when the initialization logic duplicates somewhere you can simply extract the lambda to a named function as other answers suggest

In my opinion, the safest option, if you dont want this other value (its just useless), then it may lead to really subtle bug which may be hard to find. Therefore I would throw an expectation when any of the conditions is not met:

int get_init_value(bool c1, bool c2, bool c3) {
    if (c1) { return 1; }
    else if (c2) { return 2; }
    else if (c3) { return -3; }
    throw std::logic_error("noone of conditions to define value was met");
}

That way we avoid getting some weird values that want actually match our code, but they would compile anyways ( debugging it may take a lot of time). I consider it way better than just assigning it some clearly wrong value.

Opinion based answer!

I know the example is a simplification of a real, more complex example, but IMHO it seems nowadays this kind of design issue emerge more often, and people sometimes kinda tend to over-complicate it.

Isn't it the whole purpose of a variable to hold some value? Thus isn't having a default value for this variable also a feasible thing?

So what exactly is wrong with:

int a = -1000; // or some other value meant to used for undefined
if (c1) {
  a = 1;
} else if (c2) {
  a = 2;
} else if (c3) {
  a = -3;
} 
do_something_with(a);

It is simple and readable... No lambdas, exceptions and other stuff making the code unnecessary complicated...

Or like:

int a;
if (c1) {
  a = 1;
} else if (c2) {
  a = 2;
} else if (c3) {
  a = -3;
} else {
  a = -1000;  // default for unkown state
}
do_something_with(a);

You could introduce a constant const int undefined = -1000; and use the constant.
Or an enum if c1, c2, c3 are states in some sort (which it most likely is)...
You could rearrange the code to eliminate the variable if it is not needed elsewhere.

if (c1) {
  do_something_with(1);
} else if (c2) {
  do_something_with(2);
} else if (c3) {
  do_something_with(-3);
} 

I understand your real code is much more complicated than the outline presented, but IMHO the main problem here is

should we do_something_with(a) at all if a is undefined,

rather than

what the initial value should be.

And the solution might be adding explicitly some status flag like a_is_defined to the actual parameter a instead of using magic constans.

int a = 0;
bool a_is_defined = false;

When you set them both according to c... conditions and pass them to do_something() you'll be able to make a clear distinction between a specific if(a_is_defined) {...} path and a default (error handling?) else {...}.

Or even provide separate routines to explicitly handle both paths one level earlier: if(a_is_defined) do_someting_with(a); else do_something_else();.

I would introduce a default value. I'm usually using MAX value of the type for this.

Shortest you can do this with the ternary operator like this:

#include <climits>

int a = c1 ? 1 : c2 ? 2 : c3 ? -3 : INT_MAX;

do_something_with(a);
Related