Topic: Template to access union members: legal? moral?


Author: "Philip Panyukov" <ppanyukov@googlemail.com>
Date: Tue, 19 Sep 2006 10:10:13 CST
Raw View
Greetings,

Given is a legacy API, which defines a structure with nested union
along these lines:

#define PT_LONG     1UL
#define PT_STRING8 2UL
#define PT_DOUBLE 3UL

typedef union _PV{
   long l;           // case PT_LONG
   char* lpszA;  // case PT_STRING8
   double dbl;    // case PT_DOUBLE
   // ... further 25 members
}  __UPV;

typedef struct _SPropValue {
  unsigned long ulPropType;  // descriminator
  union _PV Value;
} SPropValue;

Being lazy, I'm tired of typing things like this over and over again:

SPropValue v* = SomeAPIFunction();
if(v->ulPropType != PT_LONG)
  throw unexpected_type_expection();
else
  long longVal = v->Value.l;

I find this tedious and error-prone.  Furthermore, in most applications
I *know* the expected type at compile time already; if the type is
different from expected, it's exception.

So, I went ahead and for such cases created a template like this:

template<unsigned long pt /*descriminant*/, class T /*real type*/ >
struct SPropValueT
{
  static const T value(const SPropValue& rawPropValue)
  {
    if(rawPropValue.ulPropType != pt)
       throw unexpected_type_expection();

    // access union member via void*, acceptable??
    const void* valPtr = &rawPropValue.Value;
    const T* typedPtr = static_cast<const T*>(valPtr);
    return *typedPtr;
  }
};

// and typedefs
typedef SPropValueT<PT_LONG, long>       SPropValueLong;
typedef SPropValueT<PT_STRING8, char*> SPropValueString;

// And now usage is much better and safer.
SPropValue v* = SomeAPIFunction();
long longVal = SPropValueLong::value(v);     // ok
char* someStr = SPropValueLong::value(v);  // compile error, cannot
convert long to char* ?
------------------------
All seems to work fine with my compiler on my platform.

The question I have here:  is it legal to access union member via void*
as I did in value() function?  Does standard say anything on the
subject?  Do I invoke any other potentially undefined behaviour here?

Also, the question of applicability:  is it a valid case for using
templates at all? Is it not an overkill or otherwise "bad form"?  Of
course, the latter doesn't have anything to do with standard, so just
ignore this one :)

Many thanks for your comments,
Philip

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Tue, 19 Sep 2006 20:31:59 GMT
Raw View
Philip Panyukov ha scritto:
> Given is a legacy API, which defines a structure with nested union
> along these lines:
>
> #define PT_LONG     1UL
> #define PT_STRING8 2UL
> #define PT_DOUBLE 3UL
>
> typedef union _PV{
>    long l;           // case PT_LONG
>    char* lpszA;  // case PT_STRING8
>    double dbl;    // case PT_DOUBLE
>    // ... further 25 members
> }  __UPV;
>
> typedef struct _SPropValue {
>   unsigned long ulPropType;  // descriminator
>   union _PV Value;
> } SPropValue;
>
> Being lazy, I'm tired of typing things like this over and over again:
>
> SPropValue v* = SomeAPIFunction();
> if(v->ulPropType != PT_LONG)
>   throw unexpected_type_expection();
> else
>   long longVal = v->Value.l;
>
> I find this tedious and error-prone.  Furthermore, in most applications
> I *know* the expected type at compile time already; if the type is
> different from expected, it's exception.

IMHO, you're not being lazy, you're being intelligent. ;-)

> So, I went ahead and for such cases created a template like this:
>
> <snip>
>
>
> // And now usage is much better and safer.
> SPropValue v* = SomeAPIFunction();
> long longVal = SPropValueLong::value(v);     // ok
> char* someStr = SPropValueLong::value(v);  // compile error, cannot
> convert long to char* ?
> ------------------------
> All seems to work fine with my compiler on my platform.
>
> The question I have here:  is it legal to access union member via void*
> as I did in value() function?  Does standard say anything on the
> subject?  Do I invoke any other potentially undefined behaviour here?

It is probably correct, but I can't see right now the correct references
that makes it so. However, according to my interpretation, the following
is also legal and, despite the presence of reinterpret_cast, portable:

     const T* typedPtr = reinterpret_cast<const T*>(&valPtr);

It's because of:

9.2/16: A pointer to a POD-struct object, suitably converted using a
reinterpret_cast, points to its initial member [...] and vice versa.

and

9.5/1: [...] Each data member [of a union] is allocated as if it were
the sole member of a struct. [...]

> Also, the question of applicability:  is it a valid case for using
> templates at all? Is it not an overkill or otherwise "bad form"?  Of
> course, the latter doesn't have anything to do with standard, so just
> ignore this one :)

According to me it's a valid usage of templates. It's not overkill, it's
clever. It won't introduce any runtime overhead, but you gain a lot in
readability at a negligible cost at compile time. However, I would have
used a function template and a traits class instead of a class template,
but it's a matter of taste.

Regards,

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Wed, 20 Sep 2006 09:39:28 CST
Raw View
Philip Panyukov wrote:
> Greetings,
>
> Given is a legacy API, which defines a structure with nested union
> along these lines:
>
> #define PT_LONG     1UL
> #define PT_STRING8 2UL
> #define PT_DOUBLE 3UL
>
> typedef union _PV{
>    long l;           // case PT_LONG
>    char* lpszA;  // case PT_STRING8
>    double dbl;    // case PT_DOUBLE
>    // ... further 25 members
> }  __UPV;
>
> typedef struct _SPropValue {
>   unsigned long ulPropType;  // descriminator
>   union _PV Value;
> } SPropValue;
>
> Being lazy, I'm tired of typing things like this over and over again:
>
> SPropValue v* = SomeAPIFunction();
> if(v->ulPropType != PT_LONG)
>   throw unexpected_type_expection();
> else
>   long longVal = v->Value.l;
>
> I find this tedious and error-prone.  Furthermore, in most applications
> I *know* the expected type at compile time already; if the type is
> different from expected, it's exception.
>
> So, I went ahead and for such cases created a template like this:
>
> template<unsigned long pt /*descriminant*/, class T /*real type*/ >
> struct SPropValueT
> {
>   static const T value(const SPropValue& rawPropValue)
>   {
>     if(rawPropValue.ulPropType != pt)
>        throw unexpected_type_expection();
>
>     // access union member via void*, acceptable??
>     const void* valPtr = &rawPropValue.Value;
>     const T* typedPtr = static_cast<const T*>(valPtr);
>     return *typedPtr;
>   }
> };
>
> // and typedefs
> typedef SPropValueT<PT_LONG, long>       SPropValueLong;
> typedef SPropValueT<PT_STRING8, char*> SPropValueString;
>
> The question I have here:  is it legal to access union member via void*
> as I did in value() function?  Does standard say anything on the
> subject?  Do I invoke any other potentially undefined behaviour here?

The cast to void (besides being rather dubious) makes for an
anti-climatic result - to say the least. In particular, all of this
wonderful type safety is created, only to get tossed out of the window
at the last moment.

So to steer the implementation toward a more rewarding conclusion, I
would suggest eliminating the cast to void by adding a third template
type parameter - namely a pointer to the appropriate _PV member type.

For example:

    template <int I, class T, T _PV::*MP>
    struct PropValue
    {
        T operator()(const _PV& pv) const
        {
            if (pv->ulPropType != I)
                throw unexpected_type_exception();
            return (&pv)->*MP;
        }
    };

Now add const function object declarations like these to a global
header file:

    const PropValue< PT_LONG, long, &PV::l>         SPropValueLong;
    const PropValue< PT_STRING8, char*, &PV::lpszA> SPropValueLString8;
    const PropValue< PT_DOUBLE, double, &PV::dbl>   SPropValueDouble;

So that they can be used throughout the program like so:

    long longVal = SPropValueLong(v);   // OK
    char* someStr = SPropValueLong(v);  // Error

Greg

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "kanze" <kanze@gabi-soft.fr>
Date: Thu, 21 Sep 2006 09:35:38 CST
Raw View
Philip Panyukov wrote:

> Given is a legacy API, which defines a structure with nested
> union along these lines:

> #define PT_LONG     1UL
> #define PT_STRING8 2UL
> #define PT_DOUBLE 3UL

> typedef union _PV{
>    long l;           // case PT_LONG
>    char* lpszA;  // case PT_STRING8
>    double dbl;    // case PT_DOUBLE
>    // ... further 25 members
> }  __UPV;

> typedef struct _SPropValue {
>   unsigned long ulPropType;  // descriminator
>   union _PV Value;
> } SPropValue;

> Being lazy, I'm tired of typing things like this over and over
> again:

> SPropValue v* = SomeAPIFunction();
> if(v->ulPropType != PT_LONG)
>   throw unexpected_type_expection();
> else
>   long longVal = v->Value.l;

> I find this tedious and error-prone.  Furthermore, in most applications
> I *know* the expected type at compile time already; if the type is
> different from expected, it's exception.

> So, I went ahead and for such cases created a template like this:

> template<unsigned long pt /*descriminant*/, class T /*real type*/ >
> struct SPropValueT
> {
>   static const T value(const SPropValue& rawPropValue)
>   {
>     if(rawPropValue.ulPropType != pt)
>        throw unexpected_type_expection();
>
>     // access union member via void*, acceptable??
>     const void* valPtr = &rawPropValue.Value;
>     const T* typedPtr = static_cast<const T*>(valPtr);
>     return *typedPtr;
>   }
> };

> // and typedefs
> typedef SPropValueT<PT_LONG, long>       SPropValueLong;
> typedef SPropValueT<PT_STRING8, char*> SPropValueString;

> // And now usage is much better and safer.
> SPropValue v* = SomeAPIFunction();
> long longVal = SPropValueLong::value(v);     // ok
> char* someStr = SPropValueLong::value(v);  // compile error, cannot
> convert long to char* ?
> ------------------------
> All seems to work fine with my compiler on my platform.

> The question I have here:  is it legal to access union member
> via void* as I did in value() function?

Yes.

> Does standard say anything on the subject?

Yes.  It says that all members of a union are at the same
address, and that that address is the same as the address of the
union.

Curiously, I can't find this guarantee in the C++ standard.  The
C standard, however, says quite clearly (   6.7.2.1/14) "A pointer
to a union object, suitably converted, points to each of its
members (or if a member is a bit-field, then to the unit in
which it resides), and vice versa",  and I'm pretty sure that
the intent is that C++ be compatible with C here; I think that
there's an intent that anything that is a POD be compatible with
C.  Otherwise, it probably isn't possible to make `extern "C"'
work, at least in a useful fashion.

> Do I invoke any other potentially undefined behaviour here?

Only if somebody lies.  If the discriminator says its the
expected member, and it isn't, you get undefined behavior.

> Also, the question of applicability:  is it a valid case for using
> templates at all? Is it not an overkill or otherwise "bad form"?  Of
> course, the latter doesn't have anything to do with standard, so just
> ignore this one :)

Not at all.  I think it's a very good idea.

--
James Kanze                                           GABI Software
Conseils en informatique orient   e objet/
                   Beratung in objektorientierter Datenverarbeitung
9 place S   mard, 78210 St.-Cyr-l'   cole, France, +33 (0)1 30 23 00 34


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Philip Panyukov" <ppanyukov@googlemail.com>
Date: Sat, 23 Sep 2006 19:17:19 CST
Raw View
Thank you all for your most valuable comments.  I have a couple of
questions regarding some of them though.

Alberto Ganesh Barbati wrote:
> ...
> However, I would have
> used a function template and a traits class instead of a class template,
> but it's a matter of taste.

Alberto, would it be possible to post an example of such function
template please?

Greg Herlihy wrote:
> ....
>     template <int I, class T, T _PV::*MP>
>     struct PropValue
>     {
>         T operator()(const _PV& pv) const
>         {
>             if (pv->ulPropType != I)
>                 throw unexpected_type_exception();
>             return (&pv)->*MP;
>         }
>     };
>
> Now add const function object declarations like these to a global
> header file:
>
>     const PropValue< PT_LONG, long, &PV::l>         SPropValueLong;
>     const PropValue< PT_STRING8, char*, &PV::lpszA> SPropValueLString8;
>     const PropValue< PT_DOUBLE, double, &PV::dbl>   SPropValueDouble;
>
> So that they can be used throughout the program like so:
>
>     long longVal = SPropValueLong(v);   // OK
>     char* someStr = SPropValueLong(v);  // Error
>
> Greg
>

Greg, this template is a very interesting suggestion.  I think I
understand how it works, except for the syntax of the 3rd template
parameter (T _PV::*MP). I somehow can't quite get my head around it.
Could you please comment on this a little bit more?

Thanks,
Philip

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Alberto Ganesh Barbati <AlbertoBarbati@libero.it>
Date: Sun, 24 Sep 2006 12:57:37 CST
Raw View
===================================== MODERATOR'S COMMENT:

Approving this because it's a direct outgrowth of an on-topic discussion,
but yes: it is beginning to drift off-topic. Future discussion of
programming techniques would be better suited to c.l.c++.m.

------=_Part_781_2759043.1159120642694
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

approve<br>comment<br>Approving this because it's a direct outgrowth of an on-topic discussion, but yes: it is beginning to drift off-topic. Future discussion of programming techniques would be better suited to c.l.c++.m.<br>

------=_Part_781_2759043.1159120642694--


===================================== END OF MODERATOR'S COMMENT
Philip Panyukov ha scritto:
> Alberto Ganesh Barbati wrote:
>> ...
>> However, I would have
>> used a function template and a traits class instead of a class template,
>> but it's a matter of taste.
>
> Alberto, would it be possible to post an example of such function
> template please?

Sure. I didn't do it before because I feared it was getting off-topic
(this is comp.std.c++, so I guess we should stick to issues regarding
the standard, not discuss programming techniques). I hope the moderator
will let this pass. Here it is:

template <typename T>
const T spropvalue_cast(const SPropValue& pv)
{
    if(rawPropValue.ulPropType != Discriminator<T>::value)
       throw unexpected_type_expection();
    return *reinterpret_cast<const T*>(&pv);
}

The usage would be as simple as:

long longVal = spropvalue_cast<long>(v);

which mimicks other xxx_cast idioms (and that's the main reason for the
funny name).

The key tool is the Discriminator<T> traits class template, which could
be defined as:

template <typename T> struct Discriminator; // declared but not defined
template <> struct Discriminator<long>
  { static const unsigned value = PT_LONG; }
template <> struct Discriminator<char*>
  { static const unsigned value = PT_STRING8; }
// etc...

it may seem verbose, but it isn't much more verbose than listing all the
typedefs, which you don't need anymore.

Notice that if you try to use propvalue_cast<> with a wrong type you
will get a compile-time error. This approach has the advantage that can
be used in templates, as in:

template <typename T>
void mygenericfunc(const SPropValue& pv)
{
   T x = spropvalue_cast<T>(pv);
   // do something with x
}

You can't do that easily with the typedef approach.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Sun, 24 Sep 2006 12:58:01 CST
Raw View
Philip Panyukov wrote:
> Thank you all for your most valuable comments.  I have a couple of
> questions regarding some of them though.
>
> Greg Herlihy wrote:
> > ....
> >     template <int I, class T, T _PV::*MP>
> >     struct PropValue
> >     {
> >         T operator()(const _PV& pv) const
> >         {
> >             if (pv->ulPropType != I)
> >                 throw unexpected_type_exception();
> >             return (&pv)->*MP;
> >         }
> >     };

> Greg, this template is a very interesting suggestion.  I think I
> understand how it works, except for the syntax of the 3rd template
> parameter (T _PV::*MP). I somehow can't quite get my head around it.
> Could you please comment on this a little bit more?

Well, at least you didn't ask me to correct my mistakes first. :) But
since it's easier to explain how an example works when the example
does, in fact, work, I am delighted to announce PropValue 2.0 (that is,
the new - tested - version of PropValue);

  template <unsigned long I, class T, T _PV::*MP>
  struct PropValue
  {
      PropValue() {} // constructor required

      T operator()(const _SPropValue& sprop) const
      {
          if (sprop.ulPropType != I)
              throw unexpected_type_exception();
          return (&sprop.Value)->*MP;
      }
  };

The use of PropValue remains the same. The programmer specializes
PropValue with three arguments: the _PV union discriminator value
(ulPropType) for the specialized type, the specialized type itself, and
a data member pointer in the _PV union for the member of that type.

So the third parameter, MP, is the same kind of template parameter as
the first, I. Both are "non-type" template parameters - meaning that
the type of each is already specified and the program must provide a
(suitable) argument of that type. But MP, unlike I, has a type that is
dependent on the second argument, T. So whatever type T is, determines
the kind of _PV data member pointer that must be passed for MP. As an
example, when specializing PropValue for the type double, the program
passes the following template parameter argument list: <PT_DOUBLE,
double, &_PV::dbl>.

Now the third argument, &_PV::dbl obtains a "member pointer" for _PV's
member, dbl. Since data member pointers are rarely used in C++
programs, you may need to brush up on their syntax and use. In summary,
a member pointer is not like an ordinary pointer because it does not
specify an address in memory. Instead a member pointer of a class is
applied to an instance of that class, to obtain the address of a member
of a particular type. In fact, PropValue uses the MP member pointer in
its operator() method to obtain the address of the PV_ member of the
appropriate type.

Greg

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]