BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslmf_isbitwiseequalitycomparable

Detailed Description

Outline

Purpose

Provide a type trait for bitwise equality.

Classes

Description

This component provides a single trait metafunction, bslmf::IsBitwiseEqualityComparable, which allows generic code to determine whether objects of the specified t_TYPE can be compared using memcmp. Such types are said to be bitwise EqualityComparable. Observe that this trait may be true only for object types, even though, for example, reference types may be guaranteed identical bit representations when they refer to the same object, just as the corresponding pointer type would be bitwise EqualityComparable.

For a type to be bitwise EqalityComparable, each bit of its object representation must be significant in the value representation, and distinct sequences of bits represent different values, i.e., this trait is an assertion that the specified t_TYPE has unique representations for each possible value, and no padding bits. This property is deemed to hold for bool and enumerations where, in practice, the compiler will enforce a value representation over all the seemingly unused bits. For a C++17 tool chain, this trait should be equivalent to the std::has_unique_object_representation trait.

Note that as arrays are not allowed to introduce padding, arrays of a bitwise EqualityComparable t_TYPE are also bitwise EqualityComparable, even though they do not provide an overloaded operator==. While transforming comparisons of a single object using this trait into calls to memcmp is unlikely to be profitable, transforming comparisons of a whole array into a single memcmp call is more likely to be beneficial.

Usage

Example 1: Using the trait to optimize range comparison

Suppose we want to compare two sequences of the same object type to determine whether or not they hold the same values. The simplest solution would be to iterate over both sequences, comparing each member, and return false as soon as any pair of elements do not compare equal; if we walk all the way to the end of both sequences, then they hold the same values. If we want to perform this comparison most efficiently though, we would rather not invoke operator== on each member, and instead defer to the memcmp function in the standard library that is highly optimized (often to take advantage of platform-specific instructions) for comparing ranges of raw memory. We can switch to this other technique only if we know that the value representations of a type are unique, rely on all of the bits in their representation, and do not have strange values like NaN that self-compare as false. This property is denoted by the IsBitwiseEqualityComparable trait.

First, we create a simple struct that contains a char and a short as its two data members, and supported comparison with operator==. Note that there will be a byte of padding between the char and the short members to ensure proper alignment. We insert telemetry to count the number of times operator== is called:

namespace BloombergLP {
struct SimpleType {
// This 'struct' holds two data members with a byte of padding, and can
// be compared using the overloaded 'operator=='.
char d_dataC;
short d_dataS;
static int s_comparisons;
friend bool operator==(const SimpleType& a, const SimpleType& b)
// Return 'true' if the specified 'a' has the same value as the
// specified 'b'. Two 'SimpleType' objects have the same value if
// their corresponding 'd_dataC' and 'd_dataS' members have the
// same value. The static data member 's_comparisons' is
// incremented by one each time this function is called.
{
++s_comparisons;
return a.d_dataC == b.d_dataC
&& a.d_dataS == b.d_dataS;
}
friend bool operator!=(const SimpleType& a, const SimpleType& b)
// Return 'true' if the specified 'a' does not have the same value
// as the specified 'b'. Two 'SimpleType' objects do not have the
// same value if their corresponding 'd_dataC' and 'd_dataS'
// members do not have the same value. The static data member
// 's_comparisons' is incremented by one each time this function is
// called.
{
++s_comparisons;
return a.d_dataC != b.d_dataC
|| a.d_dataS != b.d_dataS;
}
};
int SimpleType::s_comparisons = 0;

Then, we create another struct that wraps a single int as its only data member, and supports comparison with operator==, inserting telemetry to count the number of times operator== is called:

struct SecondType {
// This 'struct' holds a single 'int' member, 'd_data', and can be
// compared using the overloaded 'operator=='.

We associate the bitwise EqualityComparable trait with SecondType using the BDE nested trait declaration facility:

int d_data;
static int s_comparisons;
friend bool operator==(const SecondType& a, const SecondType& b)
// Return 'true' if the specified 'a' has the same value as the
// specified 'b'. Two 'SecondType' objects have the same value if
// their corresponding 'd_data' elements have the same value. The
// static data member 's_comparisons' is incremented by one each
// time this function is called.
{
++s_comparisons;
return a.d_data == b.d_data;
}
friend bool operator!=(const SecondType& a, const SecondType& b)
// Return 'true' if the specified 'a' does not have the same value
// as the specified 'b'. Two 'SecondType' objects do not have the
// same value if their corresponding 'd_data' elements do not have
// the same value. The static data member 's_comparisons' is
// incremented by one each time this function is called.
{
++s_comparisons;
return a.d_data != b.d_data;
}
};
int SecondType::s_comparisons = 0;
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231
Definition bslmf_isbitwiseequalitycomparable.h:499

Next, we create another struct that wraps a single int as its only data member, and supports comparison with operator==, inserting telemetry to count the number of times operator== is called:

struct ThirdType {
// This 'struct' holds a single 'int' member, 'd_data', and can be
// compared using the overloaded 'operator=='.
int d_data;
static int s_comparisons;
friend bool operator==(const ThirdType& a, const ThirdType& b)
// Return 'true' if the specified 'a' has the same value as the
// specified 'b'. Two 'SecondType' objects have the same value if
// their corresponding 'd_data' elements have the same value. The
// static data member 's_comparisons' is incremented by one each
// time this function is called.
{
++s_comparisons;
return a.d_data == b.d_data;
}
friend bool operator!=(const ThirdType& a, const ThirdType& b)
// Return 'true' if the specified 'a' does not have the same value
// as the specified 'b'. Two 'ThirdType' objects do not have the
// same value if their corresponding 'd_data' elements do not have
// the same value. The static data member 's_comparisons' is
// incremented by one each time this function is called.
{
++s_comparisons;
return a.d_data != b.d_data;
}
};
int ThirdType::s_comparisons = 0;

We associate the bitwise EqualityComparable trait with ThirdType by explicitly specializing the trait:

namespace bslmf {
template <>
struct IsBitwiseEqualityComparable<ThirdType> : bsl::true_type {};
} // close namespace bslmf
Definition bdlbb_blob.h:576

Now, we write a function template to compare two arrays of the same type:

template <class t_TYPE>
bool rangeCompare(const t_TYPE *start, size_t length, const t_TYPE *other)
{

If we detect the bitwise EqualityComparable trait, we rely on the optimized memcmp function:

return 0 == memcmp(start,
other,
length * sizeof(t_TYPE)); // RETURN
}

Otherwise we iterate over the range directly until we find a pair of elements that do not have the same value, and return true if we reach the end of the range.

if (0 != length) {
while (*start++ == *other++) {
if (!--length) {
return true; // RETURN
}
}
}
return false;
}

Finally, we write a test to confirm that two arrays containing different values do not compare as equal (using our array comparison function), and that an array compares equal to itself, as it does comprise elements all having the same value. By inspecting the static data members provided for telemetry, we can confirm that operator== is called only for SimpleType as the other two structs dispatch to memcmp instead:

int usageExample1()
{

We confirm the initial state of the telemetry:

assert(0 == SimpleType::s_comparisons);
assert(0 == SecondType::s_comparisons);
assert(0 == ThirdType ::s_comparisons);

Then we create zero-initialized arrays for each of the types to be tested, and a second array for each type with a set of values distinct from all zeroes:

const SimpleType simpleZeroes[10] = { };
const SecondType secondZeroes[10] = { };
const ThirdType thirdZeroes [10] = { };
const SimpleType simpleValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const SecondType secondValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const ThirdType thirdValues [10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Next we confirm that the two arrays (of each type) do not compare equal, and inspect the telemetry to confirm that the comparison operator was called for only the SimpleType without the bitwise EqualityComparable trait:

assert(!rangeCompare(simpleZeroes, 10u, simpleValues) );
assert(!rangeCompare(secondZeroes, 10u, secondValues) );
assert(!rangeCompare(thirdZeroes, 10u, thirdValues) );
assert(0 < SimpleType::s_comparisons);
assert(0 == SecondType::s_comparisons);
assert(0 == ThirdType ::s_comparisons);

Then we reset the telemetry and confirm that an array of each type compares equal to itself, and inspect the telemetry to confirm that the comparison operator was called for only the SimpleType without the bitwise EqualityComparable trait:

SimpleType::s_comparisons = 0;
assert( rangeCompare(simpleValues, 10u, simpleValues) );
assert( rangeCompare(secondValues, 10u, secondValues) );
assert( rangeCompare(thirdZeroes, 10u, thirdZeroes) );
assert(0 < SimpleType::s_comparisons);
assert(0 == SecondType::s_comparisons);
assert(0 == ThirdType ::s_comparisons);
return 0;
}
} // close enterprise namespace

Example 2: Associating a Trait with a Class Template

In this example, we associate a trait not with a class, but with a class template. We create a "control" template that is not bitwise EqualityComparable, and two class templates, each of which uses a different mechanisms for being associated with the IsBitwiseEqualityComparable trait. First, we define a class template that is not bitwise EqualityComparable, NotComparable:

namespace BloombergLP {
template <class t_TYPE>
struct NotComparable
{
t_TYPE d_value;
};

Then, we define the class template PotentiallyComparable1, which uses partial template specialization to associate the IsBitwiseEqualityComparable trait with each instantiation on a t_TYPE that is itself bitwise EqualityComparable:

template <class t_TYPE>
struct PotentiallyComparable1
{
t_TYPE d_value;
};
namespace bslmf {
template <class t_TYPE>
struct IsBitwiseEqualityComparable<PotentiallyComparable1<t_TYPE> >
: IsBitwiseEqualityComparable<t_TYPE>::type {
};
} // close namespace bslmf

Next, we define the class template'PotentiallyComparable2', which uses the BSLMF_NESTED_TRAIT_DECLARATION macro to associate the IsBitwiseEqualityComparable trait with each instantiation on a t_TYPE that is itself bitwise EqualityComparable:

template <class t_TYPE>
struct PotentiallyComparable2
{
t_TYPE d_value;
PotentiallyComparable2,
};
#define BSLMF_NESTED_TRAIT_DECLARATION_IF(t_TYPE, t_TRAIT, t_COND)
Definition bslmf_nestedtraitdeclaration.h:243

Finally, we check that the traits are correctly associated by instantiating each template with types that are bitwise EqualityComparable and with types that are not not bitwise EqualityComparable, verifying the value of IsBitwiseEqualityComparable<T>::value in each case:

int usageExample2()
{
using namespace bslmf;
assert(!IsBitwiseEqualityComparable<NotComparable<int> >::value);
NotComparable<NotComparable<int> > >::value);
PotentiallyComparable1<int> >::value);
PotentiallyComparable1<NotComparable<int> > >::value);
PotentiallyComparable2<int> >::value);
PotentiallyComparable2<NotComparable<int> > >::value);
return 0;
}
} // close enterprise namespace