[go: up one dir, main page]

|
|
Log in / Subscribe / Register

Little things that matter in language design: preprocessor support?

Little things that matter in language design: preprocessor support?

Posted Jun 10, 2013 14:08 UTC (Mon) by oever (guest, #987)
In reply to: Little things that matter in language design: preprocessor support? by etienne
Parent article: Little things that matter in language design

Using preprocessor macros in C++ is strongly discouraged by Stroustrup. He says macros should only be used for include guards, something for which C++ has no other mechanism. For other cases can use constexpr, templates and

Macros allow any word (int, void, static, etc) in the code to be redefined which makes parsing the code impossible without knowing the macro definitions. I'd hate to see preprocessor use become more common.


to post comments

Little things that matter in language design: preprocessor support?

Posted Jun 10, 2013 22:00 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

Well, when I can reduce 100 lines of redundant code down to 10 with macros, I will take it without hesitation. Macros work where other constructs do not, such as in class declarations, stringification of symbols, and more. As an example, the implementation of enum <-> string functions are best written as macros to avoid typos between the case value and the actual string.

I will grant that there are times and situations where using the preprocessor is ugly and unnecessary, but that does not mean that it is always a worse solution.

Little things that matter in language design: preprocessor support?

Posted Jun 11, 2013 17:22 UTC (Tue) by cesarb (subscriber, #6266) [Link] (3 responses)

> macros should only be used for include guards, something for which C++ has no other mechanism.

The C++ standard does not have it, but every relevant implementation (even MSVC) has it: #pragma once (https://en.wikipedia.org/wiki/pragma_once).

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 14:16 UTC (Wed) by khim (subscriber, #9252) [Link] (2 responses)

MSVC actually introduced it... and it does not work.

It only works if only ever have one project, never copy headers around and thus never have two versions of the same header. In practice GCC will actually compare files which will generate many nice debugging hours if you use VCS (which tend to mess with dates of files).

Now it works:
$ mkdir lib
$ echo $'#pragma once\nint a;' > lib/test.h
$ mkdir installed
$ cp -a lib/test.h installed/test.h
$ echo $'#include "lib/test.h"\n#include "installed/test.h"' > test.c
$ gcc -E test.c -I. -o-
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "test.c"
# 1 "lib/test.h" 1

int a;
# 2 "test.c" 2

And now it does not:
$ touch installed/test.h
$ gcc -E test.c -I. -o-
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "test.c"
# 1 "lib/test.h" 1

int a;
# 2 "test.c" 2
# 1 "installed/test.h" 1

int a;
# 2 "test.c" 2

Please, don't use #pragma once - it's not worth it.

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 19:48 UTC (Wed) by dvdeug (subscriber, #10998) [Link] (1 responses)

If you have two different copies of same header in the same project, you're in deep trouble. Standard include guards will just cause you to fail in different ways whenever you hit the differences.

Little things that matter in language design: preprocessor support?

Posted Jun 14, 2013 21:47 UTC (Fri) by khim (subscriber, #9252) [Link]

Why would you fail? If newer versions of components are backward-compatible (and they should be backward compatible if they are separate components) then you just need to copy headers in proper order... which happens automatically: first updated header is in new component itself (and it's headers always included before headers from other components), then you update next component in dependencies DAG, etc.

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 11:39 UTC (Wed) by etienne (guest, #25256) [Link] (5 responses)

> For other cases can use constexpr, templates and ??

C++ (without CPP) has no way to self reference names, I mean:
printf ("Entering %s\n", __FUNCTION__);

C++ (without CPP) has no way to print/read each of the fields of a struct, the only (dirty) way is:
#undef FIELD_DEF
#define FIELD_LIST() \
FIELD_DEF(char, fieldname1, "0x%X", "%hhx") \
FIELD_DEF(unsigned, fieldname2, "0x%X", "%i")

struct mystruct {
#define FIELD_DEF(type, name, howto_print, howto_scan) type name;
FIELD_LIST()
#undef FIELD_DEF
}

void printstruct(const struct mystruct *str)
{
#define FIELD_DEF(type, name, howto_print, howto_scan) \
printf (#name howto_print "\n", str->name);
FIELD_LIST()
#undef FIELD_DEF
}

int scanstruct(struct mystruct *str, char *inputline)
{
static const char *scanf_format =
#define FIELD_DEF(type, name, howto_print, howto_scan) #name " " howto_scan " "
FIELD_LIST();
#undef FIELD_DEF

static const int scanf_nb = 0
#define FIELD_DEF(type, name, howto_print, howto_scan) + 1
FIELD_LIST();
#undef FIELD_DEF

return scanf_nb == sscanf(inputline, scanf_format,
#define FIELD_DEF(type, name, howto_print, howto_scan) &str->name,
FIELD_LIST()
#undef FIELD_DEF
);
}

C++ (without CPP) has no way to conditionally comment part of the code at compilation time (make DEBUG=1 or gcc -DDEBUG=1) so that the exact same file is kept in your source management system (no special tree for debug).
(obviously for methodologies which do allow bugs to enter the source management system, others don't need special stuff as bugs are fully denied).

C++ (without CPP) cannot manage simple special exception like a new field in a (memory mapped) structure only when generating for this special hardware.

C++ (without CPP) do not have automatic tools to remove a "conditional comment" from source code like "man unifdef"

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 14:34 UTC (Wed) by khim (subscriber, #9252) [Link] (1 responses)

C++ (without CPP) has no way to self reference names, I mean:
printf ("Entering %s\n", __FUNCTION__);

Works fine here:
$ cat test.cc
#include <stdio.h>

int main() {
  printf ("Entering %s\n", __func__);
}
$ gcc test.cc -o test
$ ./test
Entering main

Other examples looks like a classic case for the boost::serialization or other metaprogramming tricks except for the requirement to use preprocessor without preprocessor. I mean: you can't use -D directive which is preprocessor-specific... well, duh - that's directive for CPP, not for the compiler! With C++ you implement you special cases as template specializations and then just construct correct then version you actually need from the main prohgram). make DEBUG=1 works while gcc -DDEBUG=1, of course, does not.

If anything your examples support Stroustrup's position, not contradict it.

The fact that most languages out there work just fine without a CPP (even low-level ones used to interact with hardware and write standalone OSes!) says something, after all.

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 16:21 UTC (Wed) by nybble41 (subscriber, #55106) [Link]

Actually, you're still using CPP. The "#include" line should have been a clue, since that's a preprocessor directive and not valid C or C++ syntax. Try renaming the file to a ".ii" extension or passing "-x c++-cpp-output" to skip the preprocessing step.

In this case, however, the original sample would actually work, because __FUNCTION__ (and __func__) are handled by the compiler rather than CPP. The preprocessor doesn't parse the code, and consequently doesn't have any idea what the current function's name is. The __FILE__ and __LINE__ macros would be an entirely different matter.

Little things that matter in language design: preprocessor support?

Posted Jun 12, 2013 19:01 UTC (Wed) by daglwn (guest, #65432) [Link] (1 responses)

> C++ (without CPP) has no way to self reference names, I mean:
> printf ("Entering %s\n", __FUNCTION__);

True. __LINE__ is one of the few reasons I use the preprocessor.

> C++ (without CPP) has no way to print/read each of the fields of a struct,
> the only (dirty) way is:

Wow, that's totally unreadable. Lots of people want introspection and I think we'll get it soon in C++.

> C++ (without CPP) has no way to conditionally comment part of the code at
> compilation time (make DEBUG=1 or gcc -DDEBUG=1) so that the exact same
> file is kept in your source management system (no special tree for debug).

Yes, but not quite in the way you think. I prefer:

#ifdef DEBUG
const int debugEnabled = true;
#else
const int debugEnabled = false;
#endif

if (debugEnabled) { ... }

Using the preprocessor to hide code has bitten me so many times (different results with DEBUG on/off, etc.) that I just don't want to do it anymore.

> C++ (without CPP) cannot manage simple special exception like a new
> field in a (memory mapped) structure only when generating for this
> special hardware.

Not true. Template metaprogramming.

Ok, you might need one #define TARGET, but that's it.

> C++ (without CPP) do not have automatic tools to remove a "conditional
> comment" from source code like "man unifdef"

I don't have that tool and can't imagine what I'd need it for. Can you give an example?

Little things that matter in language design: preprocessor support?

Posted Jun 14, 2013 11:47 UTC (Fri) by etienne (guest, #25256) [Link]

> > C++ (without CPP) has no way to print/read each of the fields of a struct,
> > the only (dirty) way is: ...
> Wow, that's totally unreadable. Lots of people want introspection
> and I think we'll get it soon in C++.

In those few cases, I more wanted a kind of simple database (30 elements with 10 properties), not complex introspection.
Some target I have have very small memory size (256 Kbytes total internal memory on processor before the DDR-RAM is initialised; or soft-processor (written in VHDL inside an FPGA) with 96 Kbytes RAM), I cannot afford indirections and data hiding.
These macros enabled me to reduce the number of lines of source code to maintain, while keeping total control of the structure for the tens of different exceptions where you cannot use the database.

> > C++ (without CPP) do not have automatic tools to remove a "conditional
> > comment" from source code like "man unifdef"
>
> I don't have that tool and can't imagine what I'd need it for.
> Can you give an example?

If you manage big and complex software which has decades lifespan, you will have some code which is no more valid because this hardware is no more in use.
At some point nobody you know remember why this #ifdef was added, and when you try to compile with the #ifdef enabled it does not compile (for the last 3 years).
That is the right time to run "unifdef" to remove that part of code automatically from all your sources.
Sometimes these parts of code are extremely dirty hacks, made to handle the bug of an external company (don't ask, you can't get it fixed), and you really do not want to alter your design to handle that possible bug (only when you sell box A to third party which has box B).
Lucky you are if you are not forced to eat some other company's dog food for years at a time...

Little things that matter in language design: preprocessor support?

Posted Jun 13, 2013 0:01 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

The pattern I typically use for something like[1] this would be:

#define mystruct_members(call) \
call(char, fieldname1, "0x%X", "%hhx") \
call(unsigned, fieldname2, "0x%X", "%i")

struct mystruct {
#define declare_member(type, name, print, scan) type name;
mystruct_members(declare_member)
#undef declare_member
};

If you use this extensively enough, declare_member and such could be factored out into a separate header so that the same expansion for FIELD_DEF isn't used dozens of times.

Maybe it doesn't work on older compilers (passing macro names as arguments and all), but I don't see much of a reason to not use this pattern if it's available and I haven't run into a compiler that hasn't supported it where I've used it so far (granted, that includes MSVC, newer GCC, and LLVM for the projects which use this).

[1]Because C++ has a could different contexts in which things like this can be expanded, the actual meta-macro takes a "ctx" parameter as well which is then used as: "BEG(ctx) call(...) SEP(ctx) call(...) SEP(ctx) call(...) END(ctx)" so that stray semicolons are avoided and that the macro can be expanded as part of an initializer list or argument list if needed.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds