4 ms·
> If all you want is a C dialect, with smart pointers (and nothing else from C++), you can easily program that way. Really? Which dialect of c++ lets me use c9
by __david__ 12y ago
> If all you want is a C dialect, with smart pointers (and nothing else from C++), you can easily program that way.
Really? Which dialect of c++ lets me use c99 structure initializers?
C++ is no longer a superset of C.
- kazinator 12y agoAnyway, this smart pointer interface doesn't have to dictate any such a dialect to its users; it can just have C++ as a target. It was just a thought. When I work in a restricted dialect of C that is portable to C++ compilers, I have better type safety: safer treatment of void *, string literals that are const-qualified, type-safe enumerations, and safer casting operators (reinterpret_cast, const_cast, static_cast) which I can hide behind macros that work in C or C++. On the other hand, I don't have C99 designated initializers. It would be great to have both! If I had some static data that would so greatly benefit from these initializers that I cannot do without them, I might put that into a separate module that is compiled as C99. Not terribly convenient if you just want to throw a handful of static functions into an operations structure, though or whatever. There are solutions for better initialization available when you're working in C90. For each structure that is instantiated somewhere with an initializer, you can write an initializer macro instead. The macro is close to the structure definition and is maintained together. If you need a few different ways of initializing the same aggregate, then several macros can be provided that have different argument lists and different defaulting. When a new member is added (that isn't defaulted), the macros get a new argument. Either way, their expansions are carefully edited to initialize that member. The macro bodies of such initializer macros provide a complete initializer, covering every member explicitly. If the addition of a new member results in a new macro argument, the existing macro calls then do not compile; they have to be maintained. (Whereas naked initializers just keep compiling and become incorrect when structure members shuffle around: the nasty problem we want to avoid.) Ultimately this functional approach is more disciplined and safer than designated initializers. From a safety point of view, designated initializers only solve the problem of the wrong initial value going to the wrong aggregate member (that by chance has a suitable type to accept that initializer without a diagnostic). Functional initializers take a more complete approach of specifying the initialization in terms of constructor-like parameters, which are required, with the possibility of defaulting other elements to values other than zero. The one thing designated initializers do which cannot be beaten, is that you can declare a million element array and initialize element [999999] to 42, statically. This may be a bad idea; some linkage models may actually implement that by adding one million words of initialized data to your executable image. Whereas if you leave it uninitialized, and stick in the 42 at run-time, the whole object is in the "BSS" section.
- __david__ 12y agoOf course you can do things like macros, but it's ugly and becomes unreadable the more you add to the code. Structure initializers grow and expand more gracefully than anything c++ has. What does this line do? Widget *w = new Button("Press me", true, true, false, 0, 0, 0, NULL, callback, 10); Yes, you can fix that by defining special true/false enums for each of the options, or you can do something reasonable: struct Button b = { .title="Press Me", .enabled=true, .visible=true, .expand=false .x=0, .y=0, .width=10, .z_index=0, .handler=callback }; I love the array ones, but they're most useful for static look-up tables, and those are usually size constrained: void (*command[256])() = { ['H'] = command_help, ['S'] = command_start ['s'] = command_stop }; I like that a lot but I definitely use it less than the structure initializers. I hear people tout default values as a real important feature, but I've worked on large projects where backward compatibility was important, and it was almost always possible to encode the value in such a way that zero meant "default". This allows you to add new members to structures easily and anyone that doesn't set them gets the default value. It may sound hokey but it works extremely well in practice. I never once wished for default values when I was doing large C99 projects.
- kazinator 12y agoAlthough the designated initializer has the value that it shows the name of each member, so that it resembles a keyword parameter list (like what is available ins ome languages: Common Lisp, Python, ...) it is terribly weak in that there is no logic for defaulting anything other than to zero. You have no way to express that, for instance, .handler is to be mandatory. Or if it is not mandatory, that it should default to "dfl_button_callback" or whatever. Another thing you can't do is distinguish whether x was initialized to zero by default, or whether it was an explicit .x = 0. With the macro we know that since x isn't defaulted, it came from the macro. Or if it is defaulted, it couldn't have come from the macro. In the keyword arguments of the Lisp language, we know whether or not a keyword argument which has a default value was explicitly passed with that same value, or whether we are getting that value by default. (In C++, which is somewhat offtopic because the subject is what is possible in C) we can do something clearer, if a bit verbose, by defining some framework for combining traits. I have done this more than once. The result looks like: Widget *w = new Button(Label("Press me") | Enabled(true) | Callback(callback) |...)); The various traits classes have an overloaded operator | which combines traits (Traits | Traits -> Traits). The Button constructor is the Button(const Traits &) one.