views:

914

answers:

6

I have this switch statement in my code:

switch(buttonIndex){
      case 0:
         [actionSheet dismissWithClickedButtonIndex:buttonIndex animated:YES];
         break;
 case 1:
  UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
  imagePicker.delegate = self;
  imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
  [self presentModalViewController:[imagePicker autorelease] animated:YES];
  break;
 default:
  [self openEmailViewInViewController:self];
}

And at the UIImagePickerController instantiation in Case 1 I am getting an error:

error:expected expression before 'UIImagePickerController'

and I have no idea what I am doing wrong. Thoughts?

Oh, and buttonIndex is an NSInteger

A: 

Well, if I put the variable declaration before the switch statement it works fine. I guess variable declarations aren't 'expressions' in Objective C?

Alfonsol
+18  A: 

You can't declare variables right after a case label.

Chuck
+1 However, you can if you create nested scope by using { ... } to wrap the statements for a case label.
Quinn Taylor
+1  A: 

Well this isn't a proper answer, as I don't know why you see that error, but a better solution than to move the declaration outside the switch block is to make explicit scopes within the individual cases of the switch block. That usually solves any such problem with switch statements, which are a bit weird in that the case statements share scope.

So:

switch (foo) {
   case 0: {
      // notice explicit scope here
      break;
   }
   default: {
      // here as well
   }
}
harms
+1  A: 

I've seen this issue before. It has to do with labels in C, and would apply to a label used for gotos as well. The case1:, case2: lines are labels. For whatever reason, the first statement following a label must not be a declaration. I will do some research and update with more information if someone else doesn't give a good answer.

mythogen
+2  A: 

One thing I like to do in this situation is to put the contents of each case within braces. This is allowed by the compiler:

switch (buttonIndex)
{
    case 0:
    {
        [actionSheet dismissWithClickedButtonIndex:buttonIndex animated:YES];
        break;
    }
    case 1:
    {
        UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
        imagePicker.delegate = self;
        imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
        [self presentModalViewController:[imagePicker autorelease] animated:YES];
        break;
    }
    default:
    {
        [self openEmailViewInViewController:self];
    }
}
Sebastian Celis
Note: In C, you can put extra braces just about anywhere, so long as proper nesting is preserved. if (foo) {{{{{ }}}}} // is perfectly legal
Amagrammer
Thanks for your answer, this type of simplicity makes this site so useful! Keep it up.
DysonApps
+17  A: 

I ran in to this problem, and one day I decided to dig in to it.

The short non-answer but pragmatic solution:

A way to work around this "problem" is to use a semi-colon, ;, right after the colon of a case ...: statement. For example, using the example you provided, it can be "fixed" so it compiles and behaves as you would intuitively expect it to:

    case 1:; // <- Note semi-colon.
            UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
            imagePicker.delegate = self;

The long answer:

Some history: Previously, C only allowed you to declare "block local" variables at the start of a block, which was then followed by various statements. C99 changed things so you could freely intermix variable declarations and statements.

In the context of the C99 BNF grammar, a variable declaration is a declaration, and a statement is a statement. A statement means multiple things, one of those is known as a compound-statement, which is the familiar { ... } block. The ... part is loosely defined as zero or more block-items, with a block-item being defined loosely as either a declaration or a statement.

The problem lies in the way a labeled-statement (a goto label, case label, or default:, essentially the ...: statements) is defined, which is loosely defined ...: zero or more statements. It is not, as one might intuitively expect, zero or more statements or declarations. The use of a ; right after the labeled-statements : essentially terminates the zero or more statements part of a labeled-statement. This causes the grammar to fall back to the compound-statement definition, which allows for the next "statement" to be either a statement or declaration.

I have not investigated whether or not this is an unintentional over-sight of the C99 language spec (in practical terms, a bug in the C99 standard), or if this is a pragmatic concession to the complexities of writing language grammars. If you're not familiar with writing grammars, you'll notice that the above explanation allows for recursion: A labeled-statement can match case 1: case 2: case 3:. In overly simplistic terms(1), some types of grammar recursion are simple and "unambiguous", while others are complex and "ambiguous". For simplicity, most language tools will only handle the case where any ambiguity must be deterministically resolved by looking at nothing more than "the next token". I mention this only because while this might intuitively seem like a deficiency in the C99 spec, there may be compelling, non-obvious reasons why this exists... and I haven't bothered to do any further research on the subject to find out either way.

(1) This is not meant to be a technically accurate description, but a reasonable approximation for those not familiar with the issues involved.

EDIT:

The solution I gave works in "most" cases (cases being "usages", not switch cases), but it does fail in one case: This will not work when the declaration declares a C99 variable length array, such as case 1:; void *ptrs[count]; This is because in C99 it is an error to "jump past" the declaration of a C99 VLA that is in the same lexical scope where the jump took place. In these cases, you need to use case 1: { void *ptrs[count]; }. In this case, the scope of the ptrs VLA ends at the closing }. This is more complicated than it first appears because the following is perfectly legal C code, though at first glance one would intuitively think it isn't:

switch(3){
  case 0:
    printf("case 0\n");
    break;
  case 1:;
    int *ip = NULL;
    printf("case 1\n");
    break;
  case 2:
    {
      int ia[6];
      printf("case 2\n");
      break;
      case 3:
        printf("case 3\n");
        break;
      default:
        printf("default\n");
    }
}

This compiles, and when run, prints case 3.

See also: Wikipedia: Duffs Device

johne
Well, it's a lot more than *I* knew before. I just knew that for some reason C required a sub-scope if you wanted to put a declaration inside a case. Thanks.
Amagrammer
Damn, that's a good answer.
mythogen
<begins slow clap>
griotspeak