Hyrum K. Wright <hyrum_wright_at_mail.utexas.edu
<mailto:hyrum_wright_at_mail.utexas.edu?Subject=Re:%20object-model:%20Wrapping%20Subversion%20C-structs%20in%20C%2B%2B>>
wrote:
For the C++ folks out there, I've got a question about an approach to
take on the object-model branch. At issue is how to wrap the various
C structures returned to callers, particularly in a backward
compatible manner. Currently, I'm looking at svn_wc_notify_t *. As I
see it, there are a few options:
1) Just wrap the pointer to the C struct as a member of the wrapper
class.
Pros: Easy to implement; lightweight constructor.
Cons: Getters would need to translate to C++ types; would need to
implement a copy constructor which deep copies the C struct; would
also introduce pools, since creating and duplicating C structs
requires them.
2) Wrap each C struct member individually
Pros: C->C++ complexity is constrained to the constructor,
everything else is C++ types
Cons: Hard to extend for future compatibility
3) Just pass the C-struct pointer around; don't even bother with a class
Pros: Dead simple.
Cons: Requires more memory management thought by consumers; not
C++-y enough; may introduce wrapping difficulties.
I'd like to come up with something consistent, which would be used
throughout the C++ bindings. I'm also interested in a solution which
ensures the C++ bindings can be used as the basis for other
object-oriented bindings models (Python, Perl, etc.)
Thoughts?
One issue that has not been talked about in this thread
is strong typing. If you remember the problems with
Johan's diff / blame optimizations, the reason behind
it was a confusion of type semantics. Some ints were
line numbers, others were file offsets. But there was / is
no formal way to tell them apart.
Since you decided to use templates in your code, I
thought I would give it a try and design a simple template
class that allows you to define any number of int-like
types that are mutually distinct and require explicit
conversion.
It would be nice to have the C++ wrappers use these
types instead of plain ints etc. in their signatures.
-- Stefan^2.
// TypedInts.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
// extend that enum to define further types / kinds of integers
enum IntegerTypes
{
itLineNumber,
itFileOffset,
itRevision
};
// a type selection utility struct
// (X, Y, true) -> X
// (X, Y, false) -> Y
template<class First, class Second, bool get_first>
struct SelectType
{
};
template<class First, class Second>
struct SelectType<First, Second, true>
{
typedef typename First type;
};
template<class First, class Second>
struct SelectType<First, Second, false>
{
typedef typename Second type;
};
// a utility struct mapping a (potentially unsigned) integer
// to the corresponding signed integer.
template<class T>
struct DiffType
{
typedef typename T type;
};
template<>
struct DiffType<unsigned char>
{
typedef char type;
};
template<>
struct DiffType<unsigned short>
{
typedef short type;
};
template<>
struct DiffType<unsigned>
{
typedef int type;
};
template<>
struct DiffType<unsigned long>
{
typedef long type;
};
template<>
struct DiffType<unsigned long long>
{
typedef long long type;
};
// A typed integer:
// V .. base integer type (e.g. unsigned)
// T .. formal classification, i.e. this actually separates the int types
// diff_type .. if true, this is the difference type
// if false, this is the absolute value type
// TypedInt(X,Y,false) - TypedInt(X,Y,false) ->
TypedInt(X,Y,true)
//
// The arithmetics, conversions and getters have been carefully
// designed that only meaningful combinations of arguments are
// valid and everything else will be rejected by the compiler.
//
// In optimized code, this class does not impose any runtime overhead
// over plain use of built-in types.
template<class V, IntegerTypes T, bool diff_type>
class TypedInt
{
public:
// encapsulated int types
typedef typename V absolute_value_t;
typedef typename DiffType<V>::type diff_value_t;
typedef typename SelectType<diff_value_t,
absolute_value_t,
diff_type>::type value_t;
// typed integers
typedef typename TypedInt<V, T, false> absolute_t;
typedef typename TypedInt<V, T, true> diff_t;
typedef typename TypedInt<V, T, diff_type> this_t;
// expose template parameters
enum {type = T, is_diff = diff_type};
// construction, auto-conversion
TypedInt()
: value()
{
}
TypedInt(value_t value)
: value(value)
{
}
// data access
value_t& get()
{
return value;
}
const value_t& get() const
{
return value;
}
value_t* operator&()
{
return &value;
}
const value_t* operator&() const
{
return &value;
}
// assignment
TypedInt& operator=(value_t rhs)
{
value = rhs;
return *this;
}
TypedInt& operator=(const TypedInt& rhs)
{
value = rhs.value;
return *this;
}
// comparison
bool operator!() const
{
return !value;
}
bool operator==(const TypedInt& rhs)
{
return value == rhs.value;
}
bool operator!=(const TypedInt& rhs)
{
return value != rhs.value;
}
bool operator<(const TypedInt& rhs)
{
return value < rhs.value;
}
bool operator<=(const TypedInt& rhs)
{
return value <= rhs.value;
}
bool operator>(const TypedInt& rhs)
{
return value > rhs.value;
}
bool operator>=(const TypedInt& rhs)
{
return value >= rhs.value;
}
// arithmetics: increment / decrement
TypedInt& operator++()
{
++value;
return *this;
}
TypedInt operator++(int)
{
return TypedInt(value++);
}
TypedInt& operator--()
{
--value;
return *this;
}
TypedInt operator--(int)
{
return TypedInt(value--);
}
// arithmetics: prefix
const TypedInt& operator+()
{
return *this;
}
diff_t operator-()
{
return diff_t(static_cast<diff_value_t>(-value));
}
// modifying additive / differental operations:
// += abs += diff
// diff += diff
// -= abs -= diff
// diff -= diff
TypedInt& operator+=(const diff_t& rhs)
{
value += static_cast<value_t>(rhs.get());
return *this;
}
TypedInt& operator-=(const diff_t& rhs)
{
value -= static_cast<value_t>(rhs.get());
return *this;
}
// arithmetics: scaling
TypedInt& operator*=(value_t rhs)
{
value *= rhs;
return *this;
}
TypedInt operator*(value_t rhs) const
{
return TypedInt(value * value);
}
TypedInt& operator/=(value_t rhs)
{
value /= value;
return *this;
}
TypedInt operator/(value_t rhs) const
{
return TypedInt(value / value);
}
TypedInt& operator%=(value_t rhs)
{
value %= value;
return *this;
}
TypedInt operator%(value_t rhs) const
{
return TypedInt(value % value);
}
private:
value_t value;
};
// additive / differental operations:
// + abs = abs + diff
// abs = diff + abs
// diff = diff + diff
// - abs = abs - diff
// diff = abs - abs
// diff = diff - diff
template<class V, IntegerTypes T, bool diff_type>
inline TypedInt<V, T, diff_type>
operator+(const TypedInt<V, T, true>& lhs, const TypedInt<V, T, diff_type>& rhs)
{
typedef typename TypedInt<V, T, diff_type> result_t;
typedef typename result_t::value_t value_t;
return result_t (static_cast<value_t>(lhs.get()) + rhs.get());
}
template<class V, IntegerTypes T>
inline TypedInt<V, T, false>
operator+(const TypedInt<V, T, false>& lhs, const TypedInt<V, T, true>& rhs)
{
typedef typename TypedInt<V, T, false> result_t;
typedef typename result_t::value_t value_t;
return result_t (static_cast<value_t>(lhs.get()) + rhs.get());
}
template<class V, IntegerTypes T, bool diff_type>
inline TypedInt<V, T, !diff_type>
operator-(const TypedInt<V, T, false>& lhs, const TypedInt<V, T, diff_type>&
rhs)
{
typedef typename TypedInt<V, T, !diff_type> result_t;
typedef typename result_t::value_t value_t;
return result_t (static_cast<value_t>(lhs.get()) - rhs.get());
}
template<class V, IntegerTypes T>
inline TypedInt<V, T, true>
operator-(const TypedInt<V, T, true>& lhs, const TypedInt<V, T, true>& rhs)
{
typedef typename TypedInt<V, T, true> result_t;
typedef typename result_t::value_t value_t;
return result_t (lhs.get() - static_cast<value_t>(rhs.get()));
}
// some applications of the typed ints
typedef TypedInt<int, itLineNumber, false> LineNumber;
typedef TypedInt<int, itLineNumber, true> LineDiff;
typedef TypedInt<int, itRevision, false> RevisionNumber;
typedef TypedInt<int, itRevision, true> RevisionDiff;
// some test code:
int _tmain(int argc, _TCHAR* argv[])
{
RevisionNumber r0, r1(argc);
LineNumber l0(argc), l1(20);
for (RevisionNumber r = r0; r < r1; ++r)
{
if (l0 > l1)
l0 += 2;
else
l1 *= 3;
// The following statements (until the end of the block) are
// ineffective because they add zeros etc. Thus, they will be
// removed by the optimizer.
// does not compile (since these ints are different):
// r0 = l0;
// r0 += l0;
// r0 = l1 - l0;
LineDiff ld1 = l1 - l0;
LineNumber l2 = l1 - ld1;
LineDiff ld3 = ld1 - ld1;
l1 = l1 + ld3;
l0 = ld3 + l0;
ld3 = ld3 + ld3;
// does not compile (since diff - abs and abs + abs are not defined):
// l1 = ld1 - l1;
// ld3 = l1 + l1;
// does not compile (since diffs are different types than absolutes):
// l1 = ld3;
// ld3 = l1;
}
return l1.get();
}
// generated code:
/*
00000 55 push ebp
00001 8b ec mov ebp, esp
; 317 : RevisionNumber r0, r1(argc);
; 318 : LineNumber l0(argc), l1(20);
00003 8b 4d 08 mov ecx, DWORD PTR _argc$[ebp]
00006 8b d1 mov edx, ecx
00008 b8 14 00 00 00 mov eax, 20 ; 00000014H
; 319 :
; 320 : for (RevisionNumber r = r0; r < r1; ++r)
0000d 85 c9 test ecx, ecx
0000f 7e 0f jle SHORT $...@wmain
$l...@wmain:
; 321 : {
; 322 : if (l0 > l1)
00011 3b d0 cmp edx, eax
00013 7e 05 jle SHORT $...@wmain
; 323 : l0 += 2;
00015 83 c2 02 add edx, 2
; 324 : else
00018 eb 03 jmp SHORT $l...@wmain
$...@wmain:
; 325 : l1 *= 3;
0001a 8d 04 40 lea eax, DWORD PTR [eax+eax*2]
$l...@wmain:
; 319 :
; 320 : for (RevisionNumber r = r0; r < r1; ++r)
0001d 49 dec ecx
0001e 75 f1 jne SHORT $l...@wmain
$...@wmain:
00020 5d pop ebp
00021 c3 ret 0
*/