1
\$\begingroup\$

I wrote this, which I'm not sure if it's of any use yet but at least it saves me some time when writing atomics to a file or raw memory.

What I was thinking is that the conditional in the write()/read() methods isn't very good and maybe I should add methods like calcwrite()/calcread() for calculating the space required separately? Considering I don't want to use streams right now except for FILE*.

The idea is for the template to be lightweight and not too fancy because it may be included in every file of a project and such things slow down everything and dependencies make everything harder to manage.

    // This type is intended to wrap native types and other basic PODs--such as vector3, maybe?
template <typename _TBase, GDATA_TYPE _F, GRESOURCE_USAGE _U> struct supertype
{
    _TBase Value;

    typedef supertype<_TBase, _F, _U> stype;

    supertype(void) {};
    supertype(const stype& other) :Value(other.Value)   {};
    supertype(const _TBase& other):Value(other)         {};

    inline operator                 _TBase(void)    const   { return Value; };

    inline stype&                   operator    ++(void)    { ++Value; return *this; };
    inline stype&                   operator    --(void)    { --Value; return *this; };
    inline stype                    operator    ++(int)     { _TBase r = Value; ++Value; return r; };   // { return Value++; }
    inline stype                    operator    --(int)     { _TBase r = Value; --Value; return r; };   // { return Value--; }

    inline stype&                   operator    +=(const _TBase& other) { Value += other; return *this; };
    inline stype&                   operator    -=(const _TBase& other) { Value -= other; return *this; };
    inline stype&                   operator    *=(const _TBase& other) { Value *= other; return *this; };
    inline stype&                   operator    /=(const _TBase& other) { Value /= other; return *this; };

    inline stype&                   operator    &=(const _TBase& other) { Value &= other; return *this; };
    inline stype&                   operator    |=(const _TBase& other) { Value |= other; return *this; };
    inline stype&                   operator    ^=(const _TBase& other) { Value ^= other; return *this; };

    inline stype                    operator    + (const _TBase& other) const   { return Value + other; };
    inline stype                    operator    - (const _TBase& other) const   { return Value - other; };
    inline stype                    operator    * (const _TBase& other) const   { return Value * other; };
    inline stype                    operator    / (const _TBase& other) const   { return Value / other; };

    inline bool                     operator    ==(const _TBase& other) const   { return Value == other; };
    inline bool                     operator    !=(const _TBase& other) const   { return Value != other; };
    inline bool                     operator    > (const _TBase& other) const   { return Value > other; };
    inline bool                     operator    < (const _TBase& other) const   { return Value < other; };

    inline bool                     operator    ! (void) const  { return !Value; };
    inline stype                    operator    ~ (void) const  { return ~Value; };

    inline stype                    operator    & (const _TBase& other) const   { return Value & other; };
    inline stype                    operator    | (const _TBase& other) const   { return Value | other; };
    inline stype                    operator    ^ (const _TBase& other) const   { return Value ^ other; };

    inline _TBase*                  operator    & (void)        { return &Value; };
    inline const _TBase*            operator    & (void) const  { return &Value; };
    inline _TBase*                  operator    ->(void)        { return &Value; };
    inline const _TBase*            operator    ->(void) const  { return &Value; };

    // These are wrappers for memcpy() and fwrite()/fread() intended to reduce program verbosity. 
    // Disk operations return an error, while memory operations return the size copied and zero if failed to write--though in practice they may crash because of the unhandled bad access.
    inline uint32_t                 read(const char* fp)    { if( fp ) memcpy(&Value, fp, sizeof(_TBase)); return sizeof(_TBase); };
    inline uint32_t                 write(char* fp) const   { if( fp ) memcpy(fp, &Value, sizeof(_TBase)); return sizeof(_TBase); };

    inline error_t                  read(FILE* fp)          { if( 0 == fp ) { error_print("Cannot read from a null pointer."); return -1; } return (1 == fread (&Value, sizeof(_TBase), 1, fp)) ? 0 : -1; };
    inline error_t                  write(FILE* fp) const   { if( 0 == fp ) { error_print("Cannot write to a null pointer.");  return -1; } return (1 == fwrite(&Value, sizeof(_TBase), 1, fp)) ? 0 : -1; };

    // Static for lightweight type info retrieval
    static inline GDATA_TYPE        getType()   { return _F; };
    static inline GRESOURCE_USAGE   getUsage()  { return _U; };

};

For generating the GDATA_TYPE values I use the following code with a script that generates a bunch of constants:

    typedef uint32_t GDATA_TYPE;
    #define GDATATYPE_DATATYPE GDATATYPE_UINT32

    #define GTYPEID_MAKE( IsSigned, IsNorm, IsFloat, IsBigEndian, ElementCount, ElementPad, SizeInBits )    \
(                                                                                                   \
        ((IsSigned)         ? 0 : 0x80000000)                                                       \
    |   ((IsNorm)           ? 0x40000000 : 0 )                                                      \
    |   ((IsFloat)          ? 0x20000000 : 0 )                                                      \
    |   ((IsBigEndian)      ? 0x10000000 : 0)                                                       \
    |   ((((ElementCount)-1) & 0xFFFFF) <<8)                                                        \
    |   ((ElementPad)       ? 0x80 : 0)                                                             \
    |   (((SizeInBits)-1)   & 0x7F)                                                                 \
)
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! Good job on your first question. \$\endgroup\$ – SirPython Jan 28 '16 at 23:44
  • \$\begingroup\$ Thank you, Sir. I just hope my code doesn't make people too sick. \$\endgroup\$ – hackerman Jan 29 '16 at 0:34
  • \$\begingroup\$ Is GDATA_TYPE _F an integer ID indicating the wrapped type? \$\endgroup\$ – Lingxi Jan 29 '16 at 2:28
  • \$\begingroup\$ @Lingxi Yes it is. I do then something like: typedef supertype<double, GDATATYPE_FLOAT64, GUSAGE_SCALAR> sfloat64, sdouble; or typedef supertype<GMatrix4, GDATATYPE_FLOAT32_4, GUSAGE_TRANSFORM> stransform4; \$\endgroup\$ – hackerman Jan 29 '16 at 2:36
3
\$\begingroup\$

Following is my solution for your reference. It's much less code, and I think it's functionally equivalent to yours.

#include <cstddef>
#include <cstring>
#include <typeindex>
#include <typeinfo>

template <typename T, std::size_t traits = 0>
struct wrapper {
  using wrapped_type = T;

  // this one constructor would just be enough
  wrapper(T val = T{})
      : value(val) {
    // nop
  }

  // note the use of `T&` rather than `T`;
  // then no need for most operator overloads like `++`, `--`, etc
  operator T& () {
    return value;
  }
  operator const T& () const {
    return value;
  }

  T* operator &() {
    return &value;
  }
  const T* operator &() const {
    return &value;
  }
  T* operator ->() {
    return &value;
  }
  const T* operator ->() const {
    return &value;
  }

  // TODO: define `read()/write()` overloads

  // to replace `getType()`
  static std::type_index wrapped_type_index() {
    return typeid(wrapped_type);
  }

  // to replace `getUsage()`
  static constexpr std::size_t wrapped_type_traits() {
    return traits;
  }

  T value;
};

// to replace `GTYPEID_MAKE()`;
// use bit-wise OR like `is_signed | is_norm ...`
enum wrapped_type_traits : std::size_t {
  is_signed = 0x80000000,
  is_norm = 0x40000000
  // ...
};
\$\endgroup\$
  • 1
    \$\begingroup\$ In you plan to use expression templates for your math types, you probably don't need to overload the cast operator. Otherwise, I see no advantage in having the cast operator return T instead of T&. I think your implementation of the overloaded arithmetic operators is OK. \$\endgroup\$ – Lingxi Jan 29 '16 at 4:24
  • 1
    \$\begingroup\$ Also note that, since we have a class template here, the methods are only instantiated when you actually use them. So, it's fine to wrap a base type that doesn't support the full range of overloaded operators. \$\endgroup\$ – Lingxi Jan 29 '16 at 4:27
  • 1
    \$\begingroup\$ @hackerman Guess you would be interested in this. \$\endgroup\$ – Lingxi Jan 29 '16 at 5:11
  • 1
    \$\begingroup\$ @hackerman Works fine for me. See here. \$\endgroup\$ – Lingxi Jan 30 '16 at 15:49
  • 1
    \$\begingroup\$ @hackerman As people always say, MSVC is a slow child in the compiler family :-) \$\endgroup\$ – Lingxi Jan 30 '16 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.