tjcbs
|
|
« Reply #180 on: October 20, 2014, 02:58:33 PM » |
|
Personally I think the preprocessor is what's holding back C++. Template expansions, references to external functions, and private members (for struct sizing) should be resolved at link time, not compile time.
It often does, with compilers that support global optimization. But why do you care? How is it holding back c++?
|
|
|
Logged
|
|
|
|
Sik
|
|
« Reply #181 on: October 20, 2014, 10:01:20 PM » |
|
While I keep seeing people saying the preprocessor is evil... I really would like to know how would you handle stuff like this at compile time: #ifdef _WIN32 // Stuff using Windows API #else // Stuff using POSIX API #endif Some people insist that you should be using if instead, but how do you prevent the errors that arise from the involved functions and values not existing? =P (and using separate files is not really that feasible when it's only a small chunk of code that needs to be platform-specific)
|
|
|
Logged
|
|
|
|
Boreal
Level 6
Reinventing the wheel
|
|
« Reply #182 on: October 20, 2014, 11:04:01 PM » |
|
Not saying it should be gone, there are plenty of areas where having the preprocessor does help compile times and make things easier. But at the moment we're forced to choose between compile time and run time, with no option to make things happen at link time.
|
|
|
Logged
|
|
|
|
Geti
|
|
« Reply #183 on: October 20, 2014, 11:58:17 PM » |
|
@Sik: "ideally" link against something (in-house or otherwise (SDL)) that abstracts away those platform-specific APIs. We have way too much of that fragile, #ifdef-reliant platform-specific code scattered through KAG and its a pain in the arse debugging it when something goes wrong and keeping code in sync across platforms when something changes. It's a fragile way to write code that shouldn't really be present at the application/game level.
The weird middle ground that a lot of us get stuck in where >99%+ of the code is platform independent, so the rest being wrapped in preprocessor splits isn't enough of a pain to remove is pretty toxic.
Write against libraries that work the same on your target platforms, or else write platform-specific.
Extra bad points in KAG source where we've got defines for WIN32, UNIX, LINUX and OSX (the latter 2 for where linux and osx differ in the fine-print)
|
|
|
Logged
|
|
|
|
Sik
|
|
« Reply #184 on: October 21, 2014, 09:22:25 AM » |
|
And then what do we do with the code of those portable libraries? Because under the hood they're dealing with exactly the same issues =P
|
|
|
Logged
|
|
|
|
Layl
|
|
« Reply #185 on: October 21, 2014, 09:29:14 AM » |
|
And then what do we do with the code of those portable libraries? Because under the hood they're dealing with exactly the same issues =P
The code inside those libraries probably uses an external "Platform" object, one for each platform.
|
|
|
Logged
|
|
|
|
Boreal
Level 6
Reinventing the wheel
|
|
« Reply #186 on: October 21, 2014, 01:55:08 PM » |
|
What I do in terms of cross-platform support is changing back ends at link time. So if I was (for example) implementing the entry point, I would have files like entry_win32.cpp, entry_x.cpp, entry_cocoa.cpp, and so on, each implementing the same concept but with different platforms. In terms of building, I leverage CMake to make it simple. In each subsystem that requires multiple implementations, the platform-specific source goes in its own subdirectory. So for the video system, there would be video/d3d, video/gl, etc. Each subdirectory would have its own CMakeLists.txt script. Then there is a top-level CMakeLists.txt script in video that chooses which subdirectory to enter. if(WIN32) add_subdirectory(d3d) else add_subdirectory(gl) endif(WIN32)
|
|
|
Logged
|
|
|
|
Geti
|
|
« Reply #187 on: October 21, 2014, 04:56:36 PM » |
|
And then what do we do with the code of those portable libraries? Because under the hood they're dealing with exactly the same issues =P
You write them in C, where there's still the preprocessor
|
|
|
Logged
|
|
|
|
Sik
|
|
« Reply #188 on: October 21, 2014, 11:20:58 PM » |
|
And then you defeat the whole point of getting rid of it in the first place. Ideally the language should be good enough that you don't have to resort to other languages at all (except when interfacing with this written in other languages or possibly raw hardware access, nothing else).
EDIT: of course, there's the question of why the standard library of the language doesn't abstract all that in the first place.
|
|
|
Logged
|
|
|
|
Geti
|
|
« Reply #189 on: October 22, 2014, 01:54:57 AM » |
|
The joke is that "ideal" and "programming language" are in the same place at the same time, haha. Indeed, ideally the standard library would actually cover all the boilerplate crap that you need.
Unfortunately, in C++ you essentially are resorting to another language when you're writing preprocessor statements.
Re: writing the portable libraries without #ifdefs - one can of course split the modules as necessary at a file level (as you alluded to, and boreal elaborated on), and modify one's include path to include different files on different platform wrapping the needed API - the main point is that you end up with a library with the same symbols that provide the same semantics regardless of the platform; this doesn't require the preprocessor.
My view when considering the #ifdef PLATFORM #elseif OTHERPLATFORM #else #endif garbage "evil" is that it really shouldn't be in your application-level code at all; it's fragile, hard to read for nontrivial blocks of code, and can quite easily lead to a subtle warping of the semantics of your code on each platform.
|
|
|
Logged
|
|
|
|
jgrams
|
|
« Reply #190 on: October 22, 2014, 06:42:28 AM » |
|
My view when considering the #ifdef PLATFORM #elseif OTHERPLATFORM #else #endif garbage "evil" is that it really shouldn't be in your application-level code at all; it's fragile, hard to read for nontrivial blocks of code, and can quite easily lead to a subtle warping of the semantics of your code on each platform.
Yeah. I think that if you push it down to a low level, you can usually deal with only one or two parameters changing. e.g. maybe the low-level module only cares about and deals with different newline character sequences, so it's simple and clear to understand what changes. But if you just throw #ifdefs willy-nilly in your application-level code they can wind up affecting all kinds of things at once. So #ifdef WINDOWS might mean CRLF line endings, and backslashes for directory separators, and storing game config info in the registry, and, and, and...) and that's what ends up being a maintenance nightmare. If you handle the porting so that each individual choice only matters for one small module in the code, and everything else uses the consistent interface provided by that module, that sort of #ifdef seems to be pretty easy to manage. Jarzabek, Xue, Zhang, and Lee's 2009 paper Avoiding Some Common Preprocessing Pitfalls with Feature Queries has some interesting things to say about the problem in general. Edit: Oh, sorry. I've got the conversation turned around. @Sik: the two people I've known who went around telling everyone that the C preprocesser was the work of the devil were both hotheads who had a job in their past where they had to work on a project with major tangles of nested #ifdefs. Other than that I've only known people who have heard it from others, or who think that it's easily abused and you have to be careful with it (like Geti, if I'm reading that last post correctly).
|
|
« Last Edit: October 22, 2014, 08:03:26 AM by jgrams »
|
Logged
|
|
|
|
Geti
|
|
« Reply #191 on: October 22, 2014, 01:22:49 PM » |
|
I'm a bit of the first and the last I've been working in a big codebase with far too much preprocessor code for years, but I've been writing a fair bit of C recently where it's more commonly used in less evil ways I still prefer #pragma once to include guards though.
|
|
|
Logged
|
|
|
|
Boreal
Level 6
Reinventing the wheel
|
|
« Reply #192 on: October 22, 2014, 07:09:11 PM » |
|
This is why I love C. switch(mode) { case(0): if(test(a, b)) { case(1): result = foo(a, b); break; } else { case(2): result = bar(a, b); break; } } Just let that sink in for a bit.
|
|
« Last Edit: October 22, 2014, 07:14:57 PM by Boreal »
|
Logged
|
|
|
|
|
SolarLune
|
|
« Reply #194 on: October 22, 2014, 09:02:37 PM » |
|
I have no opinion on anything, just... I know all of the elements in that chunk of code, but I can't read it. It's like you threw Python into the middle of C.
|
|
|
Logged
|
|
|
|
dancing_dead
|
|
« Reply #195 on: October 22, 2014, 09:22:50 PM » |
|
This is why I love C. switch(mode) { case(0): if(test(a, b)) { case(1): result = foo(a, b); break; } else { case(2): result = bar(a, b); break; } } Just let that sink in for a bit. that actually works?
|
|
|
Logged
|
|
|
|
Geti
|
|
« Reply #196 on: October 22, 2014, 11:28:16 PM » |
|
@Boreal - perhaps more readable? certainly terser result = (!mode && test(a,b) || mode == 1) ? foo(a,b) : bar(a,b);
|
|
|
Logged
|
|
|
|
Sik
|
|
« Reply #197 on: October 23, 2014, 02:49:40 AM » |
|
Unfortunately, in C++ you essentially are resorting to another language when you're writing preprocessor statements.
OK yeah, it'd be nicer if this stuff was part of the language itself =P My view when considering the #ifdef PLATFORM #elseif OTHERPLATFORM #else #endif garbage "evil" is that it really shouldn't be in your application-level code at all; it's fragile, hard to read for nontrivial blocks of code, and can quite easily lead to a subtle warping of the semantics of your code on each platform.
I guess, the problem is when the libraries aren't helping you. E.g. in my game I use both SDL2 and PhysicsFS, and indeed the code is portable for the most part, but there are a few things (related to file paths, more specifically) where it still falls short. For example, I wanted a way to retrieve the pictures directory (said concept existing on both platforms I'm working on, and even when not I'd be happy with a fallback). Neither library provides a way to retrieve that path, so I had to write platform-specific code just for it (yes, it's wrapped into its own function so nothing else has to worry about it), and it wouldn't make much sense to make separate files just for a single 33 lines function (comments and blank lines included) when every other file function was in the same file. Ideally it'd be nice if the libraries included those functions (probably PhysicsFS, although SDL2 now has functions for retrieving some paths), but until then one has to cope with what's available =P that actually works? Yeah, the case statements are internally just labels (and as such fall under all the same restrictions as you'd have with goto - heck, you can use goto with them). Same thing works in C++ too.
|
|
|
Logged
|
|
|
|
Geti
|
|
« Reply #198 on: October 23, 2014, 03:27:19 AM » |
|
Of course, but in that case wouldn't mind the #ifdefs. That kind of code ("get the pictures folder on this platform, or a fallback inside appdata or whatever") is kind of on the border between application and engine/backend/core/whatever, but I'd say more towards the latter - it's going in with your file operations, which are also generally reasonably low level. I'd potentially consider splitting out your file operations to an internal (static) lib to be maintained over time and reused between projects, though a header and implementation file is of course ok too. ...This is quite the tangent, haha
|
|
|
Logged
|
|
|
|
Rusk
Level 1
|
|
« Reply #199 on: October 23, 2014, 04:23:22 AM » |
|
This is why I love C. switch(mode) { case(0): if(test(a, b)) { case(1): result = foo(a, b); break; } else { case(2): result = bar(a, b); break; } } Just let that sink in for a bit. That's cool. Suddenly the switch statement makes sense, rather than being just some awkward syntax to be memorized. Thanks!
|
|
|
Logged
|
|
|
|
|