4

I have a sort of bizarre wish; I don't know if any compiler or language extension out there allows this.

I want to be able to declare variables inside a function invocation, like this:

int test(int *out_p) {
    *out_p = 5;
    return 1;
}

int main()
{
    if (int ret = test(int &var)) { // int var declared inside function invocation
        fprintf(stderr, "var = %d\n", var); // var in scope here
    }
    return 0;
}

because then the scoping of var follows the scoping of ret. For another example (from a project I'm working on now), I have

cmd_s = readline();
int x, y, dX, dY, symA, symB;
if (sscanf(cmd_s, "placeDomino:%d %d atX:%d y:%d dX:%d dY:%d",
                           &symA, &symB, &x,  &y,   &dX,  &dY) == 6) {
    do_complicated_stuff(symA, symB, x, y, dX, dY);
} else if (sscanf(cmd_s, "placeAtX:%d y:%d dX:%d dY:%d", &x, &y, &dX, &dY) == 4) {
    do_stuff(x, y, dX, dY);
    /* symA, symB are in scope but uninitialized :-( so I can accidentally
     * use their values and the compiler will let me */
}

and I would prefer to write

cmd_s = readline();
if (sscanf(cmd_s, "placeDomino:%d %d atX:%d y:%d dX:%d dY:%d",
                    int &symA, int &symB, int &x, int &y, int &dX, int &dY) == 6) {
    do_complicated_stuff(symA, symB, x, y, dX, dY);
} else if (sscanf(cmd_s, "placeAtX:%d y:%d dX:%d dY:%d", int &x, int &y, int &dX, int &dY) == 4) {
    do_stuff(x, y, dX, dY);
    /* Now symA, symB are out of scope here and I can't
     * accidentally use their uninitialized values */
}

My question is, does any compiler support this? Does gcc support it if I rub it the right way? Is there a C or C++ (draft) spec that has this?

Edit: just realized that in my first code example, my declaration of int ret is also no good in C99; I guess I'm spoiled by for loops. I want that feature too; imagine

while(int condition = check_condition()) {
    switch(condition) {
        ...
    }
}

or something like that.

zmccord
  • 2,398
  • 1
  • 18
  • 14
  • Would you sacrifice clarity for brevity? – vulkanino Feb 27 '12 at 10:33
  • I did say "bizarre wish" :-p I generally find brevity improves clarity; this is why I prefer to use idioms that afford me brevity where I can find them. I'm probably out of luck on this particular idiom, though. – zmccord Feb 27 '12 at 10:44
  • @vulkanino Of course. Do you have any idea how much keyboards cost these days? We are talking of at least $10 every decade in keyboard costs! Clearly, wear and tear of the keyboard should be the primary concern of any successful programmer. – Lundin Feb 27 '12 at 12:42
  • @zmccord You should probably clarify whether your interest is to use this in production code, or if you are just curious about the inner workings of the C language. In case of the latter, add the tag "language-lawyer" to the post, to prevent it from getting close votes. – Lundin Feb 27 '12 at 12:48
  • I do actually want this feature for production code; I also, although I failed to make this sufficiently clear in the original post, am aware that this is not standard in C99, and was fishing more for extended dialects that had this feature. My example from the project I'm working on is in earnest; I'd like this feature to abbreviate my code and help the compiler catch my mistakes in this real-life project. – zmccord Feb 27 '12 at 13:24
  • Well... "because then the scoping of var follows the scoping of ret" doesn't make any sense. You will gain nothing from this, unless you have a horribly obfuscated function with so many identifiers that you get namespace collisions within the local scope. In that case, your problem is the fundamental program design and not the scope. If you worry about program efficiency, please read my answer posted. – Lundin Feb 27 '12 at 14:35
  • Many seem to not see the use of this, but I believe such a syntax would be useful for making sure an output variable is usable before it gets used. Many function returns success / fail and sets output parameters. The values of the output parameters may be unchanged, or worse, undefined if the function fails. It would be useful have the ability to make those output parameters only usable when the function succeeds. – Rufus Oct 02 '18 at 06:06
  • Since C++17 you could use std::optional to ensure that your variables have been given a value. – mcabreb May 29 '19 at 09:24

4 Answers4

5

Besides block scope declarations, in C99 there are basically two other ways to declare variables that are by definition restricted to the statement in which they occur:

  • Compound literals are of the form (type name){ initializers } and declare a local variable that lives in the current block. E.g for a function call you could use test(&(int){ 0 }).
  • for scope variables only have the scope of the for statement itself and the depending statement or block.

Your if expression with local variable you could do something weird like

for (bool cntrl = true; cntrl; cntrl = false)
   for (int ret = something; cntrl && test(&ret); cntrl = false) {
      // use ret inside here
   }

Be careful, such things quickly become unreadable. On the other hand, optimizers reduce such code quite efficiently to the essential and easily find out that test and the inner side of the for block are only evaluated once.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • And what exactly have we gained from this, aside from interesting [IOCCC](http://www.ioccc.org/) material? – Lundin Feb 27 '12 at 12:46
  • @Lundin, it suppose your allusion is about the hidden `for` scope variable? Perhaps, that it was not a good idea to only import the concept of block-statement local variables halfway from C++? – Jens Gustedt Feb 27 '12 at 13:37
  • The compound literal form is really useful for functions which take boxed values, where you need to wrap a C type in a box and pass it to a function. i.e. #define BOXED_INT32(_val) &(value_box_t){ .type = INT32, .datum.int32 = _val} my_boxy_func(BOXED_INT32(42)) – Arran Cudbard-Bell May 11 '17 at 23:27
  • Could probably be combined with C11 _Generic (to create a BOX macro which automatically selected the box type based on the C type) so long as there was a 1:1 mapping between boxed types and C types. – Arran Cudbard-Bell May 11 '17 at 23:33
1
while(int condition = check_condition()) {
    switch(condition) {
        ...
    }
}

or

if (int ret = test(int &var))

My question is, does any compiler support this? Does gcc support it if I rub it the right way? Is there a C or C++ (draft) spec that has this?

This is not C. The clause for an if statement or a while statement has to be an expression and cannot be a declaration.

You can only have a declaration since C99 for the for iteration statement in its first clause:

for (clause-1; expression-2; expression-3)

The clause-1 can be a declaration or an expression.

ouah
  • 142,963
  • 15
  • 272
  • 331
0

Use an empty scope, like this:

int test(int *out_p) {
    *out_p = 5;
    return 1;
}

int main()
{
    {
        int var, ret;
        if (ret = test(&var)) {
            fprintf(stderr, "var = %d\n", var); // var in scope here
        }
    }
    // var not in scope
    return 0;
}
Johan
  • 1,094
  • 1
  • 8
  • 4
  • Yes, that does work; but it lacks brevity. I guess what I suggested isn't that much more concise, but I was hoping for at least a little syntactic sugar. – zmccord Feb 27 '12 at 10:24
  • 3
    this is not C, the `if` clause has to be an expression and cannot be a declaration – ouah Feb 27 '12 at 10:30
  • Copy paste from OP :-) I moved the declaration to the same level as the var. – Johan Feb 27 '12 at 10:35
0

No compiler supports this. I fail to see where this would make sense.

To reduce the number of source code lines does not necessarily lead to a more efficient program, this is a common misunderstanding. In 99% of the cases in C, it does not make sense to rewrite a statement like this to a more compact one. It only leads to less readable code, you would get the very same machine code in the end.

What you should do is this:

void some_func (void) // good example
{
  ... lots of code here

  int ret;
  int var;

  ret = test(&var);
  if(ret == SOMETHING)
  {
    fprintf(stderr, "var = %d\n", var); // var in scope here
  }
}

What you should not do is this:

void some_func (void) // bad example
{
  ... lots of code here

  {
    int ret;
    int var;

    if((ret = test(&var))
    {
      fprintf(stderr, "var = %d\n", var); // var in scope here
    }
  }
}

The good example and the bad example will yield exactly the same machine code. This is very important to understand!

First of all, the reduced scope of the variables in the bad example will not lead to a more effective program: the compiler is very capable of knowing when a variable is used for the first time and when it is not used any longer. In both examples the compiler will store the variables ret and var in CPU registers or on the stack.

Also note that whether the variables are declared in the middle of the function (C99/C11 only) or at the beginning (C90 or C99/C11) doesn't matter what-so-ever for efficiency. Declaring variables in the middle of a scope is just a programming style feature: you are telling the reader of the code that this variable starts to matter from this point on. The compiler, as opposed to the human reading the code, doesn't care where you wrote the declaration.

Had we omitted ret and just checked the result of test(), that wouldn't have made any difference either - the result of the function must still be saved somewhere, either in a variable explicitly declared by the programmer or in a temporary variable implicitly created by the compiler. The machine code will be the same, just harder to debug if there is no ret variable.

The main difference between the examples is that the bad one contains widely-recognized bad programming practice, such as assignment inside condition (MISRA-C:2004 13.1) and implicit test against zero for non-boolean variables (MISRA-C:2004 13.2). Add the needless, obscure, additional local scope to that.

So my advice is to study some assembler (or disassembly) and learn how C code is actually translated to machine code. When you know that, you won't feel the need to needlessly obfuscate code with the false assumption that you are improving it.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I actually cared none at all about the output assembly, and in fact the "needless" local scope was exactly the only thing I wanted. There is no need to be condescending; I'm aware that modern compilers have such niceties as liveness analysis and register coloring. I'm sorry for triggering your pet peeve about stupid "optimization", *which I share*, but I would appreciate your saving your rant for people who are actually trying to stupidly optimize. – zmccord Feb 27 '12 at 16:06
  • @zmccord My point was, if your argument isn't optimization, the only argument left is readability and maintenance, which you are notably _decreasing_ with changes like this. – Lundin Feb 28 '12 at 07:39
  • I suppose I'm probably decreasing the readability to C hackers. I'm more used to more featureful languages; the functionality I get by passing pointers to values in C I can get simply by returning values in other languages. For example, in perl I can say "my ($foo, $bar) = baz();" and both foo and bar are bound at once at the same time as the function call; I can do similar things in SML and Haskell. I wanted to be able to mimic that familiar pattern in order to *increase* readability, debuggability and maintainability, but I suppose that's more opaque to someone whose preferred language is C. – zmccord Feb 29 '12 at 19:53
  • Since the language I'm writing in is C, that's an argument for your position; the code is likely to be read by C programmers. Happily, although I hadn't mentioned this, I am working on a project whose sole programmer is yours truly, so what other people think of my style idiosyncrasies is less relevant. In the main you are correct; most C programmers would care little for this feature and I wouldn't use it for a project where I was sharing the code with other people. – zmccord Feb 29 '12 at 19:54
  • 1
    They added a semantically similar feature in [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/), called "out variables", with the restriction that in C# it is limited to function arguments marked as `out`. – ceztko Aug 10 '17 at 21:46