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:
# 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
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 */
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.