[glib/c-cxx-std-versions: 1/7] gmacros: Define G_CXX_STD_VERSION and check macros




commit 0372ffe97bade4ec4684daaa9550b287796d9f8f
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Wed Sep 14 01:03:22 2022 +0200

    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                       | 118 ++++++++++++++++++++++++++++++-
 glib/tests/macros.c                      |   6 ++
 glib/tests/meson.build                   |   2 +-
 6 files changed, 189 insertions(+), 4 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in
index 90b0f32585..144e7077b0 100644
--- a/docs/reference/glib/glib-sections.txt.in
+++ b/docs/reference/glib/glib-sections.txt.in
@@ -411,6 +411,10 @@ G_HAVE_GNUC_VISIBILITY
 G_GNUC_INTERNAL
 G_GNUC_MAY_ALIAS
 
+<SUBSECTION>
+G_CXX_STD_VERSION
+G_CXX_STD_CHECK_VERSION
+
 <SUBSECTION>
 G_DEPRECATED
 G_DEPRECATED_FOR
diff --git a/glib/docs.c b/glib/docs.c
index f93ce80483..a96e067fbe 100644
--- a/glib/docs.c
+++ b/glib/docs.c
@@ -2167,6 +2167,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.
+ *
+ * |[<!-- language="C" -->
+ * #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 f9e20407ee..a915a8b7a2 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 6ac60791c8..e99f6f21c0 100644
--- a/glib/tests/cxx.cpp
+++ b/glib/tests/cxx.cpp
@@ -19,6 +19,115 @@
 
 #include <glib.h>
 
+#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;
@@ -34,7 +143,7 @@ test_typeof (void)
   MyObject *obj3 = g_atomic_pointer_get (&obj2);
   g_assert_true (obj3 == obj);
 
-#if __cplusplus >= 201103L
+#if G_CXX_STD_CHECK_VERSION (11)
   MyObject *obj4 = nullptr;
 #else
   MyObject *obj4 = NULL;
@@ -42,7 +151,7 @@ test_typeof (void)
   g_atomic_pointer_set (&obj4, obj3);
   g_assert_true (obj4 == obj);
 
-#if __cplusplus >= 201103L
+#if G_CXX_STD_CHECK_VERSION (11)
   MyObject *obj5 = nullptr;
   g_atomic_pointer_compare_and_exchange (&obj5, nullptr, obj4);
 #else
@@ -195,12 +304,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, nullptr);
 #else
   g_test_init (&argc, &argv, 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 cbbc0f6def..b18f3a3a6d 100644
--- a/glib/tests/macros.c
+++ b/glib/tests/macros.c
@@ -33,6 +33,12 @@
 # endif
 #endif
 
+#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 8b5c58b8c5..429218cf54 100644
--- a/glib/tests/meson.build
+++ b/glib/tests/meson.build
@@ -168,7 +168,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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]