How can I define an UUID for a class, and use __uuidof, in the same way for g++ and Visual C++?

懵懂的女人 提交于 2019-11-29 11:47:43

Up-front disclaimer: nothing of this has been extensively tested or reviewed. I just wrote it.

One possible unification approach is suggested by this fact:

  • A Visual C++ __declspec( uuid ) doesn’t need to be provided with the first declaration of a class: it can be applied after the first declaration.

E.g. Visual C++ code can look like this:

class Foo
{};

class  __declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) ) Foo;

Thus a which-compiler-is-it sniffing macro CPPX_UUID_FOR can be defined as …

#if !defined( CPPX_UUID_FOR )
#   if defined( _MSC_VER )
#       define CPPX_UUID_FOR    CPPX_MSVC_UUID_FOR
#   elif defined( __GNUC__ )
#       define CPPX_UUID_FOR    CPPX_GNUC_UUID_FOR
#   endif
#endif

and be invoked after the first declaration of the class:

#include <iostream>
using namespace std;

struct Foo {};
CPPX_UUID_FOR( Foo, "dbe41a75-d5da-402a-aff7-cd347877ec00" );

void test()
{
    using cppx::uuid::operator<<;
    cout << setw( 20 ) << "__uuidof: " << __uuidof( Foo ) << endl;
}

The macro implementation for Visual C++ is trivial:

#define CPPX_MSVC_UUID_FOR( name, spec )    \
    class __declspec( uuid( spec ) ) name

The macro implementation for g++ is a bit more involved:

#define CPPX_GNUC_UUID_FOR( name, spec )    \
template<>                                  \
inline                                      \
auto __mingw_uuidof<name>()                 \
    -> GUID const&                          \
{                                           \
    using cppx::operator"" _uuid;           \
    static constexpr GUID the_uuid = spec ## _uuid; \
                                            \
    return the_uuid;                        \
}                                           \
                                            \
template<>                                  \
inline                                      \
auto __mingw_uuidof<name*>()                \
    -> GUID const&                          \
{ return __mingw_uuidof<name>(); }          \
                                            \
static_assert( true, "" )

… where the static_assert only serves to support a final semicolon in the invocation.

The user defined literal is not strictly necessary, but I thought it was interesting to do it.

cppx::operator"" _uuid is defined thusly, in namespace cppx:

namespace detail {
    CPPX_CONSTEXPR
    auto uuid_from_spec( char const* const s, size_t const size )
        -> cppx::Uuid
    {
        return (
            size == 36?   cppx::uuid::from(
                    reinterpret_cast<char const (&)[37]>( *s )
                            ) :
            cppx::fail(
                "An uuid spec must be 36 chars, like"
                " \"dbe41a75-d5da-402a-aff7-cd347877ec00\""
                )
            );
    }
}  // namespace detail

#if !(defined( _MSC_VER ) || defined( NO_USER_LITERALS ))
CPPX_CONSTEXPR
auto operator"" _uuid( char const* const s, size_t const size )
    -> cppx::Uuid
{ return detail::uuid_from_spec( s, size ); }
#endif

where cppx::uuid::from is defined earlier in namespace cppx::uuid:

inline CPPX_CONSTEXPR
auto from( char const (&spec)[37] )
    -> Uuid
{ return Initializable( ce, spec ); }

where ce is just a constructor tag, of enumeration type Const_expr, that selects the constexpr constructor of the uuid::Initializable class:

struct Initializable: Uuid
{
    explicit CPPX_CONSTEXPR
    Initializable( Const_expr, char const (&spec)[37] )
        : Uuid( {
                // Data1
                (((((((((((((
                    static_cast<unsigned long>( nybble_from_hex( spec[0] ) )
                    << 4) | nybble_from_hex( spec[1] ))
                    << 4) | nybble_from_hex( spec[2] ))
                    << 4) | nybble_from_hex( spec[3] ))
                    << 4) | nybble_from_hex( spec[4] ))
                    << 4) | nybble_from_hex( spec[5] ))
                    << 4) | nybble_from_hex( spec[6] ))
                    << 4) | nybble_from_hex( spec[7] ),
                // Data2
                static_cast<unsigned short>(
                    (((((
                        static_cast<unsigned>( nybble_from_hex( spec[9] ) )
                        << 4) | nybble_from_hex( spec[10] ))
                        << 4) | nybble_from_hex( spec[11] ))
                        << 4) | nybble_from_hex( spec[12] )
                    ),
                // Data 3
                static_cast<unsigned short>(
                    (((((
                        static_cast<unsigned>( nybble_from_hex( spec[14] ) )
                        << 4) | nybble_from_hex( spec[15] ))
                        << 4) | nybble_from_hex( spec[16] ))
                        << 4) | nybble_from_hex( spec[17] )
                    ),
                // Data 4
                {
                    static_cast<unsigned char>( byte_from_hex( spec[19], spec[20] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[21], spec[22] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[24], spec[25] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[26], spec[27] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[28], spec[29] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[30], spec[31] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[32], spec[33] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[34], spec[35] ) )
                }
            } )
    {}

    explicit
    Initializable( char const (&spec)[37] )
        : Uuid()
    {
        for( int i = 0;  i < 8;  ++i )
        {
            Uuid::Data1 = (Uuid::Data1 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[8] == '-' );
        for( int i = 9;  i < 13;  ++i )
        {
            Uuid::Data2 = (Uuid::Data2 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[13] == '-' );
        for( int i = 14; i < 18;  ++i )
        {
            Uuid::Data3 = (Uuid::Data3 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[18] == '-' );
        for( int i = 19; i < 23;  i += 2 )
        {
            Uuid::Data4[(i - 19)/2] = byte_from_hex( spec[i], spec[i + 1] );
        }
        assert( spec[23] == '-' );
        for( int i = 24; i < 36;  i += 2 )
        {
            Uuid::Data4[2 + (i - 24)/2] = byte_from_hex( spec[i], spec[i + 1] );
        }
    }
};

The two constructors mainly differ in how easy it is to judge the correctness or not of the code, but the last one (I wrote that first!) also has useful assert statements. I'm not sure how to best do such assertions for the constexpr constructor. Or even whether that is doable, which is one reason why there are two constructors instead of just one.

Oh, the << invocations here are just good old left-shifts, not fancy custom operator-notation output or stream or store operations. :)


The definitions of nybble_from_hex and byte_from_hex are pretty trivial, but the fail function is a bit subtle. In spite of appearances it’s not a constexpr function. Instead, it’s a non-returning function. C++11 has a notation to express that, [[noreturn]], but as far as I know neither Visual C++ nor g++ supports that yet. So instead I use compiler specific annotations, like this:

#if !defined( CPPX_NORETURN )
#   if defined( _MSC_VER )
#       define CPPX_NORETURN    __declspec( noreturn )
#   elif defined( __GNUC__ )
#       define CPPX_NORETURN    __attribute__((noreturn))
#   else
#       define CPPX_NORETURN    [[noreturn]]
#   endif
#endif

and then fail can be coded up simply as e.g.

struct Whatever
{
    template< class Type >
    CPPX_CONSTEXPR operator Type () const { return Type(); }
};

inline
CPPX_NORETURN
auto fail( string const& s )
    -> Whatever
{ throw runtime_error( s ); }

I found it non-trivial (and possibly impossible) to express fail as a constexpr function when it has std::string argument, and as an ordinary function calls of it suppressed the constexpr property. The non-returning variant works OK with g++. However, I’m not sure what the standard has to say about this.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!