Aficionados of
COM may remember - because it's been a while, hey? - that the
VARIANT structure is a composite of nested
union and
struct aggregates, as in:
typedef struct tagVARIANT {
union {
struct {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
. . . // and lots more members
USHORT *puiVal;
ULONG *pulVal;
ULONGLONG *pullVal;
INT *pintVal;
UINT *puintVal;
struct {
PVOID pvRecord;
IRecordInfo *pRecInfo;
} __VARIANT_NAME_4;
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
} VARIANT;
(The full definition is given in
https://docs.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-variant.)
The relevant aspect(s) for this post is the apparent use of member names for the anonymous
struct/
union aggregates -
__VARIANT_NAME_1,
__VARIANT_NAME_2, etc. They are present because anonymous aggregates are not standard C++ and, prior to
C11, were not part of standard C, even though they are commonly supported on many compilers: on supporting compilers, the apparent names are
#defined to nothing; on non-supporting compilers they are
#defined to, respectively,
n1,
n2,
n3,
brecVal, as in:
#if ... some condition ...
# define __VARIANT_NAME_1 n1
# define __VARIANT_NAME_2 n2
# define __VARIANT_NAME_3 n3
# define __VARIANT_NAME_4 brecVal
#else
# define __VARIANT_NAME_1
# define __VARIANT_NAME_2
# define __VARIANT_NAME_3
# define __VARIANT_NAME_4
#endif
This is all fine and well, except when you're writing COM code to work in C++ as well as in C, as is done for various components within the
COMSTL project, as in the
comstl_C_DECIMAL_compare() function (in
comstl/util/DECIMAL_functions.h):
int
comstl_C_DECIMAL_compare(
DECIMAL const* lhs
, DECIMAL const* rhs
) /* noexcept */
{
. . .
COMSTL_ACCESS_VARIANT_vt_BYPTR(&vdecL) = VT_DECIMAL;
COMSTL_ACCESS_VARIANT_decVal_BYPTR(&vdecL) = *lhs;
. . .
}
In order to make it compile for both C and C++, the function is implemented in terms of COMSTL macros such as
COMSTL_ACCESS_VARIANT_vt_BYREF(), which is defined as:
#if defined(COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_)
# define COMSTL_ACCESS_VARIANT_vt_BYPTR(pvar) \
(pvar)->__VARIANT_NAME_1.__VARIANT_NAME_2.vt
#else
# define COMSTL_ACCESS_VARIANT_vt_BYPTR(pvar) (pvar)->vt
#endif
If the (internal / undocumented)
COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_ macro is defined, then
COMSTL_ACCESS_VARIANT_vt_BYPTR() obtains the
vt member via the full retinue of named members; if not, then it obtains
vt directly.
So, the question is, how is the
COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_ macro discriminated?
It is defined in
comstl/comstl.h, and up to STLSoft 1.10.1 beta-16, it was done as:
#if !defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES) && \
defined(_FORCENAMELESSUNION) && \
!defined(NONAMELESSUNION)
# define COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES
#endif
#if defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#elif defined(STLSOFT_COMPILER_IS_GCC)
/* GCC has different definitions to the other compilers, so have to treat
* differently
*/
# if defined(NONAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif /* NONAMELESSUNION */
#else /* ? compiler */
/* Other compilers use the MS headers, which test against __STDC__,
* _FORCENAMELESSUNION and NONAMELESSUNION
*/
# if ( __STDC__ && \
!defined(_FORCENAMELESSUNION)) || \
defined(NONAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif /* ( __STDC__ && !_FORCENAMELESSUNION) || NONAMELESSUNION */
#endif /* compiler */
However, when compiling for MinGW GCC 8.1 today, this no longer suffices.
Searching the web for
_FORCENAMELESSUNION and
NONAMELESSUNION yielded little, so I did a thorough search of the implementation headers for a bunch of compilers and the Windows SDK, in order to get a (more) complete and up-to-date definition, arriving at the following - hopefully final - definition, which will appear in STLSoft 1.10.1 beta-17:
#if 1 && \
!defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES) && \
!defined(_FORCENAMELESSUNION) && \
defined(NONAMELESSUNION) && \
1
# define COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES
#endif
#if 0
#elif defined(_FORCENAMELESSUNION)
#elif defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#else
/* The observed extant discriminations are:
*
* 1 (VC++ 6):
*
* (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION)
*
* 2 (*):
*
* (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION) || (!defined(_MSC_EXTENSIONS) && !defined(_FORCENAMELESSUNION))
*
* 3 (MinGW GCC 4.9):
*
* NONAMELESSUNION
*
* 4 (MinGW GCC 8.1):
*
* (__STDC__ && !defined(__cplusplus) && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION) || (defined (_MSC_VER) && !defined(_MSC_EXTENSIONS) && !defined(_FORCENAMELESSUNION))
*
* which may be better understood as:
*
* 1 (VC++ 6):
0 ||
defined(NONAMELESSUNION) ||
( __STDC__ && \
!defined(_FORCENAMELESSUNION)) ||
0
*
* 2 (*):
0 ||
defined(NONAMELESSUNION) ||
( __STDC__ &&
!defined(_FORCENAMELESSUNION)) ||
( !defined(_MSC_EXTENSIONS) &&
!defined(_FORCENAMELESSUNION)) ||
0
*
* 3 (MinGW GCC 4.9):
0 ||
defined(NONAMELESSUNION) ||
0
* 4 (MinGW GCC 8.1):
0 ||
defined(NONAMELESSUNION) ||
( __STDC__ &&
!defined(__cplusplus) &&
!defined(_FORCENAMELESSUNION)) ||
( defined (_MSC_VER) &&
!defined(_MSC_EXTENSIONS) &&
!defined(_FORCENAMELESSUNION)) ||
0
*/
# if 0
# elif defined(NONAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# elif defined(STLSOFT_COMPILER_IS_GCC)
# if STLSOFT_GCC_VER >= 80000 /* NOTE: this number may be wrong - too large, but still old way with 4.9 */
# if 0
# elif __STDC__ && \
!defined(__cplusplus) && \
!defined(_FORCENAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# elif defined(_MSC_VER) && \
!defined(_MSC_EXTENSIONS) && \
!defined(_FORCENAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif /* ? GCC version */
# endif
# elif __STDC__ && \
defined(_FORCENAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# elif !defined(_MSC_EXTENSIONS) && \
!defined(_FORCENAMELESSUNION)
# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif
#endif
If anyone else needs to understand
_FORCENAMELESSUNION and
NONAMELESSUNION, I hope this post can help.