-
-
Save fosterbrereton/1439318ab60e1d02d8f9315631ac7fd9 to your computer and use it in GitHub Desktop.
| /* | |
| MIT License | |
| Copyright 2019 Foster T. Brereton | |
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |
| associated documentation files (the "Software"), to deal in the Software without restriction, | |
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | |
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all copies or substantial | |
| portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |
| OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| */ | |
| /**************************************************************************************************/ | |
| #include <iostream> | |
| /*************************************************************************************************** | |
| # How to use this document | |
| The code can be compiled simply: | |
| ```shell | |
| clang++ -DBMBF_PRODUCT_CONFIG=DESKTOP better_macros_better_flags.cpp && ./a.out | |
| ``` | |
| It should print out a series of notes about compilation state. | |
| `BMBF_PRODUCT_CONFIG` can be any one of `DESKTOP`, `MOBILE`, or `EMBEDDED`, and will produce | |
| slightly different output. | |
| ***************************************************************************************************/ | |
| /* | |
| PRODUCT macros denote what product (target) is being built. There is exactly one product defined | |
| per translation unit. The product macro is defined at the project level, and must precede any | |
| preprocessing. | |
| */ | |
| #define BMBF_PRODUCT_PRIVATE_DEFINITION_DESKTOP() 0 | |
| #define BMBF_PRODUCT_PRIVATE_DEFINITION_MOBILE() 1 | |
| #define BMBF_PRODUCT_PRIVATE_DEFINITION_EMBEDDED() 2 | |
| #define BMBF_PRODUCT_XSMASH(X, Y) X##Y() | |
| #define BMBF_PRODUCT_SMASH(X, Y) BMBF_PRODUCT_XSMASH(X, Y) | |
| #define BMBF_PRODUCT(X) \ | |
| (BMBF_PRODUCT_PRIVATE_DEFINITION_##X() == \ | |
| BMBF_PRODUCT_SMASH(BMBF_PRODUCT_PRIVATE_DEFINITION_, BMBF_PRODUCT_CONFIG)) | |
| /**************************************************************************************************/ | |
| /* | |
| PLATFORM macros denote operating-system- or machine-level features. They are automatically | |
| derived based on built-in preprocessor definitions defined at compile-time. It is common to have | |
| more than one platform defined per translation unit. | |
| */ | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_APPLE() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_IOS() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_IOS_SIMULATOR() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_LINUX() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_MACOS() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_MICROSOFT() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_POSIX() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_UWP() 0 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_WIN32() 0 | |
| #define BMBF_PLATFORM(X) BMBF_PLATFORM_PRIVATE_DEFINITION_##X() | |
| #if defined(_WIN32) | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_MICROSOFT | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_MICROSOFT() 1 | |
| #if defined(WINAPI_FAMILY) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_UWP | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_UWP() 1 | |
| #else | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_WIN32 | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_WIN32() 1 | |
| #endif | |
| #elif defined(__APPLE__) | |
| #include "TargetConditionals.h" | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_POSIX | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_POSIX() 1 | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_APPLE | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_APPLE() 1 | |
| #if TARGET_OS_SIMULATOR | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_IOS | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_IOS() 1 | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_IOS_SIMULATOR | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_IOS_SIMULATOR() 1 | |
| #elif TARGET_OS_IPHONE | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_IOS | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_IOS() 1 | |
| #elif TARGET_OS_MAC | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_MACOS | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_MACOS() 1 | |
| #endif | |
| #elif defined(__LINUX__) | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_POSIX | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_POSIX() 1 | |
| #undef BMBF_PLATFORM_PRIVATE_DEFINITION_LINUX | |
| #define BMBF_PLATFORM_PRIVATE_DEFINITION_LINUX() 1 | |
| #endif | |
| /**************************************************************************************************/ | |
| /* | |
| FEATURE macros define what application-level features should be included in the compilation. The | |
| set of features is always derived from a combination of the target product and platform(s). | |
| There are any number of features defined per translation unit. | |
| */ | |
| #define BMBF_FEATURE_PRIVATE_DEFINITION_APPLE_DESKTOP() \ | |
| BMBF_PLATFORM(APPLE) && BMBF_PRODUCT(DESKTOP) | |
| #define BMBF_FEATURE_PRIVATE_DEFINITION_MICROSOFT_MOBILE() \ | |
| BMBF_PLATFORM(MICROSOFT) && BMBF_PRODUCT(MOBILE) | |
| #define BMBF_FEATURE_PRIVATE_DEFINITION_EMBEDDED_LINUX() \ | |
| BMBF_PLATFORM(LINUX) && BMBF_PRODUCT(EMBEDDED) | |
| #define BMBF_FEATURE_PRIVATE_DEFINITION_METAL() \ | |
| BMBF_PLATFORM(APPLE) && !BMBF_PLATFORM(IOS_SIMULATOR) | |
| #define BMBF_FEATURE(X) BMBF_FEATURE_PRIVATE_DEFINITION_##X() | |
| /**************************************************************************************************/ | |
| int main(int argc, char** argv) { | |
| #if BMBF_PRODUCT(DESKTOP) | |
| std::cout << "Hello, Desktop!\n"; | |
| #endif | |
| #if BMBF_PRODUCT(MOBILE) | |
| std::cout << "Hello, Mobile!\n"; | |
| #endif | |
| #if BMBF_PRODUCT(EMBEDDED) | |
| std::cout << "Hello, Embedded!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(APPLE) | |
| std::cout << "Hello, Apple!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(IOS) | |
| std::cout << "Hello, iOS!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(IOS_SIMULATOR) | |
| std::cout << "Hello, iOS Simulator!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(LINUX) | |
| std::cout << "Hello, Linux!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(MACOS) | |
| std::cout << "Hello, macOS!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(MICROSOFT) | |
| std::cout << "Hello, Microsoft!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(POSIX) | |
| std::cout << "Hello, POSIX!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(UWP) | |
| std::cout << "Hello, UWP!\n"; | |
| #endif | |
| #if BMBF_PLATFORM(WIN32) | |
| std::cout << "Hello, Win32!\n"; | |
| #endif | |
| #if BMBF_FEATURE(METAL) | |
| std::cout << "Hello, Metal feature!\n"; | |
| #endif | |
| #if BMBF_FEATURE(APPLE_DESKTOP) | |
| std::cout << "Hello, Apple desktop feature!\n"; | |
| #endif | |
| #if BMBF_FEATURE(MICROSOFT_MOBILE) | |
| std::cout << "Hello, Microsoft mobile feature!\n"; | |
| #endif | |
| #if BMBF_FEATURE(EMBEDDED_LINUX) | |
| std::cout << "Hello, embedded Linux feature!\n"; | |
| #endif | |
| } |
I am glad to hear you got something valuable out of the article. I have added the MIT license to the top of the gist. Please let me know if this is not sufficient.
Hi @fosterbrereton! I just discovered your blog post on this, and find it quite useful. I'm converting some preprocessor code in my project to use function-like macros so that I get an error if the macro is unintentionally undefined. That risk has always made me nervous; it's nice to have a clean solution!
I have a question, though. It seems like there is still a vulnerability, and I can't find a way around it. Suppose I have:
// header
#define MY_FUNC_LIKE_MACRO() 1
// .cpp
#if MY_FUNC_LIKE_MACRO
... do stuff ...
#endif
I've forgotten the parentheses, and this compiles just fine, and it does the wrong thing – the compiler thinks that MY_FUNC_LIKE_MACRO has not been defined, and so the stuff is not done. Is there a way to remove this vulnerability? I tried also defining MY_FUNC_LIKE_MACRO as something illegal, like:
// header
#define MY_FUNC_LIKE_MACRO |
#define MY_FUNC_LIKE_MACRO() 1
so that use of MY_FUNC_LIKE_MACRO without the parens would error, but the compiler considers the MY_FUNC_LIKE_MACRO() definition as re-defining MY_FUNC_LIKE_MACRO; both definitions can't co-exist. What is really needed in C/C++ is a #if-like preprocessor directive that errors if the thing being tested is not defined. The above, with the missing parens, is really a special case of the more general problem that a typo will still silently do the wrong thing, like:
#if MY_FUN_LIKE_MACRO()
... stuff is not done because MY_FUN_LIKE_MACRO is a typo ...
#endif
It would be nice for typos like this to generate an error, somehow. Got a graceful solution?
Hey Foster. Great article on FluentCpp and great solution. Right now, from tracing it back to the article, the snippet runs under Creative Commons license CC BY-NC-SA 4.0, "unless stated otherwise". I'd really love to see this snippet under e.g. MIT, so potential commercial use is not a showstopper. Would this be possible?