Syntactic sugar in C/C++
Asked Answered
C

10

34

I have been looking into Ruby and find its keywords "until" and "unless" very interesting. So I thought what was a good way to add similar keywords into C/C++. This is what I came up with:

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

I am looking for some suggestions on this. Can anyone suggest a better alternative?

Here is an example of a program that I wrote to illustrate what I intended to do:

#include <stdio.h>
#include <stdlib.h>

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

unsigned int factorial(unsigned int n) {
    unsigned int fact=1, i;
    until ( n==0 )
        fact *= n--;
    return fact;    
}

int main(int argc, char*argv[]) {
    unless (argc==2)
        puts("Usage: fact <num>");
    else {
        int n = atoi(argv[1]);
        if (n<0)
            puts("please give +ve number");
        else
            printf("factorial(%u) = %u\n",n,factorial(n));
    }
    return 0;
}

It would be great if you could point me to some references for similar tricks that can be employed in C or C++.

Cila answered 9/4, 2011 at 19:23 Comment(9)
It seems like you've already done it.Wes
although these look very fancy at first, it's more appropriate to use standard C++ instead even if you programm just for yourself. In teamwork this is a no go. Your code will look unfamiliar. I remember a certain sugar.h containing Pascal like definitions...Ropeway
I don't think I like unless with an else clause (I love when/unless in lisp, however). I'm not really impressed by an until loop but may be because I've never used it before.Adp
I can't see how this is useful. Why two new keywords for something that is logically and easily done with existing ones?Cassidycassie
@houbysoft: while I agree this is bad practice, the argument could be made that expressiveness is a virtue. "do this until that" is more expressive than "do this while not that" even though they mean the same thing. Or rather, they express different things; a terminating condition rather than a continuing condition. Much like how while can be easily implemented via if... goto, but the former for succinctly expresses your desire to loop.Pitiable
I agree that this is bad practise. I do not intend to do this in a code-base or anything involving some more programmers. However, I find this quite nifty for short codes (mostly for coding competitions or algo problems).Cila
You might be interested in this project: lolcode.com which also add some syntactic sugar... :DTamarah
If you must do this, my only suggestion is to spell until properly.Millenary
My suggestion is to not use define #define, make a transpiler so people can still read it in C.Instantly
O
17

The way you did it seems to me the correct way to do it, if you're going to do it at all. Because the expansion of the macro is so similar to what you'd expect[1], I think it's valid to make the macro look like syntax (), rather than the usually recommended SCARY_UPPERCASE_MACROS() which are used to show that this code doesn't follow usual syntax and you should only use it carefully.

[1] The only flaw being the inability to declare variables, which is unlikely anyway, and likely to produce an error in the right place when used incorrectly, rather than doing something weird.

Furthermore, even small increases in readability are important, so being able to say until ( instead of while (! really does make it easier to read many loops. If the ending condition is more easily thought of as an exceptional condition (regardless of whether it is or not) writing the loop that way round makes it easier to read. So even though it is only syntactic sugar, I think there's reason to consider it.

However I don't think it's worth it. The benefit is small, since most programmers are used to reading if (! and the cost is real: Anyone reading the code will have to check whether this a macro, or a custom compiler, and whether or no it does what they think. And it may misleadingly make you think you can do things like i=5 unless xxxx;. Such little improvements, if widespread, would fragment the language, so often it's best to do things the standard way, and adopt improvements slowly.

However, it can be done well: the entirety of boost and tr1, especially the stuff done with templates to look like extensions to the library, involves extending C++ in various ways, many of which aren't adopted as they didn't seem worth it, but many of which have small or very widespread take-up because they made real improvements.

Ostiole answered 12/4, 2011 at 11:10 Comment(1)
The first paragraph of this answer is problematic, to say the least! Macros are not namespaced; they are totally global. So it is terrible to give them short names that might be used elsewhere. If you define a macro with a short name such as until, and then you include a 3rd party header which declares a member function until, that header will be broken. It will try to declare void until(int a); but instead will be saying void while(!(int a)); Good luck deciphering that error message! And so (unsurprisingly) Boost uses scary uppercase names with prefixes for all its macro tricks.Altdorf
V
112

Can anyone suggest a better alternative?

Yes. Don't do this at all. Just use the while and if statements directly.

When you're programming in C or C++, program in C or C++. While until and unless might be used frequently and idiomatic in some languages, they are not in C or C++.

Vermiculite answered 9/4, 2011 at 19:26 Comment(4)
+1 on this. It may be idiomatic to do this in Ruby, but you're not programming in Ruby are you? Don't try to force a language to act like another, you're just going to write bad code that way.Pinsky
I know that in a big environment coding like this will only invite trouble. But my question was what was the best way to go about doing this; and not about whether to do this or not.Cila
@BiGYaN: The best way to go about doing this is not to go about doing this at all.Vermiculite
That's !useful.Moonfish
O
17

The way you did it seems to me the correct way to do it, if you're going to do it at all. Because the expansion of the macro is so similar to what you'd expect[1], I think it's valid to make the macro look like syntax (), rather than the usually recommended SCARY_UPPERCASE_MACROS() which are used to show that this code doesn't follow usual syntax and you should only use it carefully.

[1] The only flaw being the inability to declare variables, which is unlikely anyway, and likely to produce an error in the right place when used incorrectly, rather than doing something weird.

Furthermore, even small increases in readability are important, so being able to say until ( instead of while (! really does make it easier to read many loops. If the ending condition is more easily thought of as an exceptional condition (regardless of whether it is or not) writing the loop that way round makes it easier to read. So even though it is only syntactic sugar, I think there's reason to consider it.

However I don't think it's worth it. The benefit is small, since most programmers are used to reading if (! and the cost is real: Anyone reading the code will have to check whether this a macro, or a custom compiler, and whether or no it does what they think. And it may misleadingly make you think you can do things like i=5 unless xxxx;. Such little improvements, if widespread, would fragment the language, so often it's best to do things the standard way, and adopt improvements slowly.

However, it can be done well: the entirety of boost and tr1, especially the stuff done with templates to look like extensions to the library, involves extending C++ in various ways, many of which aren't adopted as they didn't seem worth it, but many of which have small or very widespread take-up because they made real improvements.

Ostiole answered 12/4, 2011 at 11:10 Comment(1)
The first paragraph of this answer is problematic, to say the least! Macros are not namespaced; they are totally global. So it is terrible to give them short names that might be used elsewhere. If you define a macro with a short name such as until, and then you include a 3rd party header which declares a member function until, that header will be broken. It will try to declare void until(int a); but instead will be saying void while(!(int a)); Good luck deciphering that error message! And so (unsurprisingly) Boost uses scary uppercase names with prefixes for all its macro tricks.Altdorf
B
14

This reminded me of something I have seen in someone's code:

#define R return;

Besides, making the code hard to comprehend, you increase maintenance costs.

Breadth answered 9/4, 2011 at 19:30 Comment(0)
D
8

I suggest it would be better not use them.

You cannot use them in Ruby style as

`printf("hello,world") unless(a>0);`

is illegal.

And it would be more difficult for C programmers to understand the code. Meanwhile the extra macro could be a problem.

Dispute answered 9/4, 2011 at 19:29 Comment(1)
a > 0 && printf("Hello, world!") ; ?Breathed
A
5

If you're going to define macros, it's good practise to make them look really ugly. In particular, they should be all-capitals, and have some kind of prefix. This is because there is no namespacing and no coordination with the type system or overload resolution of C++.

So if your macro was called BIGYAN_UNNECESSARY_MACRO_UNTIL then it would be not quite "beyond the pale".

If you want to extend C++ with new looping constructs, consider investigating lambdas in C++0x, where you could allow:

until([&] { return finished; }, [&] 
{
    // do stuff
});

It's not perfect, but it's better than macros.

Altdorf answered 9/4, 2011 at 19:30 Comment(4)
Your proposed use of lambdas for this will give me nightmares tonightAdp
I am conflicted: that usage of lambdas is horrible and awesome at the same time. I like it, though.Vermiculite
For something as simple as re-implementing while(!b) it's clearly overkill.Altdorf
It is indeed horrible and awesome. However this is quite normal code (and quite clean) in FP languages. The ability to define its own control structure is very very useful.Pleuro
R
5

I don't think your macros are bad in particular if they are used only in your own code base. This article might be interesting for you. That being said, I see some downsides in your macros when we use them in C++.
For example, we cannot write as:

until (T* p = f(x)) ...
unless (T* p = f(x)) ...

on the other hand, we can write as:

while (T* p = f(x)) ...
if (T* p = f(x)) ...

As for unless, if we define it as:

#define unless(x) if (x) {} else

then we can write unless (T* p = f(x)) .... However, in this case we cannot add else clause after it.

Romney answered 9/4, 2011 at 20:36 Comment(2)
From only being a C coder this caught my eye. You cannot do this in C (the question is tagged as C as well as C++). I can barely believe you can do it in C++... Why would you even want to?Typehigh
@JamesMorris: Yes, the downsides stuff are true only when we use the macros in C++. Thank you for pointing out. I edited the answer.Romney
P
3

Look at how boost foreach is done.

The header defines BOOST_FOREACH (the ugly, prefixed macro). You can

#define foreach BOOST_FOREACH

in you .cpp files in order to have cleaner code. You should not do it in your .h files however and use the ugly BOOST_FOREACH instead.

Now, here is a set of “functional-programming-ish” macros for “convenient” IF THEN ELSE expressions (because ?: is ugly):

#define IF(x) (x) ?
#define ELSE :

now

int x = IF(y==0) 1
        ELSE IF(y<0) 2*y
        ELSE 3*y;

desugarises into:

int x = (y==0) ? 1 : (y<0) ? 2*y : 3*y;
Pleuro answered 9/4, 2011 at 23:51 Comment(0)
D
2

Good syntax sugar example (IMHO):

struct Foo {
    void bar() {}
};
typedef std::vector<Foo*> FooVector;
typedef boost::ptr_vector<Foo> FooPtrVector;

FooVector v1;
for (FooVector::iterator it = v1.begin(); it != v1.end(); ++it)
    (*it)->bar(); // ugly

FooPtrVector v2;
for (FooPtrVector::iterator it = v2.begin(); it != v2.end(); ++it)
    it->bar(); // nice
Darreldarrell answered 22/8, 2012 at 8:30 Comment(0)
P
1

As peoples said, adding those word do not really offer a useful syntactic sugar, because the cost to read a while ( or a if (! is small, all C developers are used to, and using such macro you'll scary most of the C developers. Also, making a language look like an other isn't a good idea.

BUT, syntactic sugar matters. As already stated, in C++, boost add lot's of syntactic sugar through templates, and the stl also provide Somme sugar (for example, std::make_pair(a, b) is a syntactic sugar for std::pair<decltype(a), decltype(b)>(a, b).

As a language improve, both functionalities and syntactic sugar are added to improve readability, writability, and efficiency of developers. For example, with the C++11 spec, the "for (elements in datastructure)" was added (see below), and also the "auto" keyword which allow a week inference of types (I say weak because you need to type a lot's of types at a lots of places where the type is actually 'obvious' and redundant).

Also, in haskell, using monads without the do notation (syntactic sugar) would be a real pain, and no-one would be using them1.


An example without syntactic sugar:

//C++ < 11
std::vector<int> v;
v.push_back(3);
v.push_back(7);
v.push_back(9);
v.push_back(12);
for (std::vector<int>::iterator it = v.begin();
     it != v.end();
     it++)
{
    std::cout << *it << std::endl;
}

And with syntactic sugar:

//C++ >= 11
std::vector<int> v {3, 7, 9, 12};

for (auto elm : v)
{
    std::cout << elm << std::endl;
}

A bit more readable, no?


An haskell example for the IO monad (from HaskellWiki) :

f :: IO String
f =
  ask "What's your name ?" >>= \name ->
  putStrLn "Write something." >>= \_ ->
  getLine >>= \string ->
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string) >>= \_ ->
  return name

g :: IO String    
g = do
  name <- ask "What's your name ?"
  putStrLn "Write something."
  string <- getLine
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string)
  return name

Here is a link to ideone : http://ideone.com/v9BqiZ


1: Actually, the language is more flexible than C++ and allow creating operators (for example &^, +., :+:, ...), so we could imagine that someone would quickly introduce syntactic sugar again :).

Perineurium answered 22/7, 2013 at 12:49 Comment(0)
I
-1

Well you could do it, but make sure that it's not in the source file. I reccomend taking the CoffeeScript approach to JavaScript without the optimization generation.

Just in general you should write your language but export, give, and have the transpiled code as if you would have written it in C with extreme compatability.

Try looking into awk and make it transpile all files with ending .cugar on save or something similar. :)

Good luck.

Instantly answered 1/10, 2018 at 15:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.