From f9845abe3981286e3227434eeb9ca2b9069b30fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 01:03:22 +0200 Subject: [PATCH] gmacros: Define G_CXX_STD_VERSION and check macros Sadly, in C++ there's not an universal way to get what language standard is used to compile GLib-based programs, in fact while most compilers relies on `__cplusplus`, MSVC is defining that, but it does not use it to expose such information (unless `/Zc:__cplusplus` arg is used). On the other side, MSVC reports the language standard via _MSVC_LANG [1]. This complication makes us defining some macros in a very complex way (such as glib_typeof()), because we need to perform many checks just to understand if a C++ compiler is used and what standard is expecting. To avoid this, define multiple macros that can be used to figure out what C++ standard is being used. [1] https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 --- docs/reference/glib/glib-sections.txt.in | 4 + glib/docs.c | 38 ++++++++ glib/gmacros.h | 25 +++++ glib/tests/cxx.cpp | 114 ++++++++++++++++++++++- glib/tests/macros.c | 6 ++ glib/tests/meson.build | 2 +- 6 files changed, 187 insertions(+), 2 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index face9dec6..82a34683c 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -410,6 +410,10 @@ G_HAVE_GNUC_VISIBILITY G_GNUC_INTERNAL G_GNUC_MAY_ALIAS + +G_CXX_STD_VERSION +G_CXX_STD_CHECK_VERSION + G_DEPRECATED G_DEPRECATED_FOR diff --git a/glib/docs.c b/glib/docs.c index 1ca7927fb..041d90ce3 100644 --- a/glib/docs.c +++ b/glib/docs.c @@ -2527,6 +2527,44 @@ * Since: 2.6 */ +/** + * G_CXX_STD_VERSION: + * + * The C++ standard version the code is compiling against, it's defined + * with the same value of `__cplusplus` for C++ standard compatible + * compilers, while it uses `_MSVC_LANG` in MSVC, given that the + * standard definition depends on a compilation flag in such compiler. + * + * This is granted to be undefined when not compiling with a C++ compiler. + * + * See also: %G_CXX_STD_CHECK_VERSION + * + * Since: 2.76 + */ + +/** + * G_CXX_STD_CHECK_VERSION: + * @version: The C++ version to be checked for compatibility + * + * Macro to check if the current compiler supports a specified @version + * of the C++ standard. Such value must be numeric and can be provided both + * in the short form for the well-known versions (e.g. `11`, `17`...) or in + * the complete form otherwise (e.g. `201103L`, `201703L`, `205503L`...). + * + * When a C compiler is used, the macro is defined and returns always %FALSE. + * + * This value is compared against %G_CXX_STD_VERSION. + * + * |[ + * #if G_CXX_STD_CHECK_VERSION(20) + * #endif + * ]| + * + * Returns: %TRUE if @version is supported by the compiler, %FALSE otherwise + * + * Since: 2.76 + */ + /** * G_LIKELY: * @expr: the expression diff --git a/glib/gmacros.h b/glib/gmacros.h index 36d2238fc..c892c0b44 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -64,6 +64,31 @@ #define G_GNUC_EXTENSION #endif +#if !defined (__cplusplus) + +# undef G_CXX_STD_VERSION +# define G_CXX_STD_CHECK_VERSION(version) (0) + +#else /* defined (__cplusplus) */ + +# if defined (_MSVC_LANG) +# define G_CXX_STD_VERSION (_MSVC_LANG > __cplusplus ? _MSVC_LANG : __cplusplus) +# else +# define G_CXX_STD_VERSION __cplusplus +# endif /* defined(_MSVC_LANG) */ + +# define G_CXX_STD_CHECK_VERSION(version) ( \ + ((version) >= 199711L && (version) <= G_CXX_STD_VERSION) || \ + ((version) == 98 && G_CXX_STD_VERSION >= 199711L) || \ + ((version) == 03 && G_CXX_STD_VERSION >= 199711L) || \ + ((version) == 11 && G_CXX_STD_VERSION >= 201103L) || \ + ((version) == 14 && G_CXX_STD_VERSION >= 201402L) || \ + ((version) == 17 && G_CXX_STD_VERSION >= 201703L) || \ + ((version) == 20 && G_CXX_STD_VERSION >= 202002L) || \ + 0) + +#endif /* !defined (__cplusplus) */ + /* Every compiler that we target supports inlining, but some of them may * complain about it if we don't say "__inline". If we have C99, or if * we are using C++, then we can use "inline" directly. Unfortunately diff --git a/glib/tests/cxx.cpp b/glib/tests/cxx.cpp index aa5e0cb57..a2f8e4e3f 100644 --- a/glib/tests/cxx.cpp +++ b/glib/tests/cxx.cpp @@ -19,6 +19,115 @@ #include +#if !defined (G_CXX_STD_VERSION) || !defined (G_CXX_STD_CHECK_VERSION) +#error G_CXX_STD_VERSION is not defined +#endif + +G_STATIC_ASSERT (G_CXX_STD_VERSION); + +#if G_CXX_STD_VERSION >= 199711L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (98)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (199711L)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (03)); +#endif + +#if G_CXX_STD_VERSION == 199711L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201103L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201402L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201103L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (98)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (199711L)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (03)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201103L)); +#endif + +#if G_CXX_STD_VERSION == 201103L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201402L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201402L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201402L)); +#endif + +#if G_CXX_STD_VERSION == 201402L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201703L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201703L)); +#endif + +#if G_CXX_STD_VERSION == 201703L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 202002L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION == 202002L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (23)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202300L)); +#endif + +#ifdef _G_EXPECTED_CXX_STANDARD +static void +test_cpp_standard (void) +{ + guint64 std_version = 0; + + if (!g_ascii_string_to_unsigned (_G_EXPECTED_CXX_STANDARD, 10, 0, G_MAXUINT64, + &std_version, NULL)) + { + g_test_skip ("Expected standard value is non-numeric: " + _G_EXPECTED_CXX_STANDARD); + return; + } + +#if !G_GNUC_CHECK_VERSION (11, 0) + if (std_version >= 20) + { + // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93821 + g_test_skip ("Expected standard version is not properly supported by compiler"); + return; + } +#endif + + g_test_message ("Checking if '" G_STRINGIFY (G_CXX_STD_VERSION) "' respects " + "the expected standard version '" _G_EXPECTED_CXX_STANDARD "'"); + g_assert_true (G_CXX_STD_CHECK_VERSION (std_version)); + + if (std_version < 10 || std_version > 90) + std_version = 97; + + if (std_version >= 90) + g_assert_cmpuint (G_CXX_STD_VERSION, >=, (std_version + 1900) * 100); + else + g_assert_cmpuint (G_CXX_STD_VERSION, >=, (std_version + 2000) * 100); +} +#endif + typedef struct { int dummy; @@ -186,12 +295,15 @@ test_steal_pointer (void) int main (int argc, char *argv[]) { -#if __cplusplus >= 201103L +#if G_CXX_STD_CHECK_VERSION (11) g_test_init (&argc, &argv, NULL); #else g_test_init (&argc, &argv, static_cast(NULL)); #endif +#ifdef _G_EXPECTED_CXX_STANDARD + g_test_add_func ("/C++/check-standard-" _G_EXPECTED_CXX_STANDARD, test_cpp_standard); +#endif g_test_add_func ("/C++/typeof", test_typeof); g_test_add_func ("/C++/atomic-pointer-compare-and-exchange", test_atomic_pointer_compare_and_exchange); g_test_add_func ("/C++/atomic-pointer-compare-and-exchange-full", test_atomic_pointer_compare_and_exchange_full); diff --git a/glib/tests/macros.c b/glib/tests/macros.c index efe632b52..38372518b 100644 --- a/glib/tests/macros.c +++ b/glib/tests/macros.c @@ -22,6 +22,12 @@ #include +#ifdef G_CXX_STD_VERSION +#error G_CXX_STD_VERSION should be undefined in C programs +#endif + +G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (98)); + /* Test that G_STATIC_ASSERT_EXPR can be used as an expression */ static void test_assert_static (void) diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 2af5871ff..3ec63508b 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -187,7 +187,7 @@ if have_cxx 'cxx-@0@'.format(std) : { 'source' : ['cxx.cpp'], 'suite' : ['cpp'], - 'cpp_args' : [arg], + 'cpp_args' : [arg, '-D_G_EXPECTED_CXX_STANDARD="@0@"'.format(std)], }, } endforeach