Outline
Purpose
Provide clean compile-time dispatch based on multiple traits
Classes
- See also
Description
Usage
This section illustrates the intended usage of this component.
Example 1: Dispatch On Traits
We would like to create a function template, ScalarPrimitives::copyConstruct
, that takes an original object and an allocator constructs a copy of original
using the most efficient valid mechanism. The function should take into account that the original type might be bitwise copyable, or have an allocator that can be different in the copy than in the original object, or that the original might be a pair type, where the correct method of copying first
and second
is (recursively) governed by the same concerns.
The old (legacy) bsls::HasTrait
mechanism has a clumsy mechanism for dispatching on multiple traits at once. For example, the bslalg::scalarprimitives::copyConstruct
, function uses four different implementations, depending on the traits of the object being copied. The existing code looks like this:
template <class TARGET_TYPE>
inline
void
ScalarPrimitives::copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
enum {
VALUE = HasTrait<TARGET_TYPE,
TypeTraitUsesBslmaAllocator
>::value ? Imp::USES_BSLMA_ALLOCATOR_TRAITS
: HasTrait<TARGET_TYPE,
TypeTraitBitwiseCopyable
>::value ? Imp::BITWISE_COPYABLE_TRAITS
: HasTrait<TARGET_TYPE,
TypeTraitPair
>::value ? Imp::PAIR_TRAITS
: Imp::NIL_TRAITS
};
Imp::copyConstruct(address, original, allocator,
}
Definition bslma_allocator.h:457
#define BSLS_ASSERT_SAFE(X)
Definition bsls_assert.h:1762
Definition bslmf_integralconstant.h:244
We would like to replace the cumbersome chain of ?:
operations with a clean mechanism for producing one of four different types based on the first matching trait.
First, we create three traits metafunctions to replace the three legacy traits used above:
Note that these definitions are simplified to avoid excess dependencies; A proper traits definition would inherit from bslmf::DetectNestedTrait
instead of from bsl::false_type
.
Next, we forward-declare bslma::Allocator
and bslalg::scalarprimitives::copyConstruct
:
namespace bslma {
class Allocator; }
struct ScalarPrimitives {
template <class TARGET_TYPE>
const TARGET_TYPE& original,
};
Definition bdlc_flathashmap.h:1805
Definition balxml_encoderoptions.h:68
static void copyConstruct(TARGET_TYPE *address, const TARGET_TYPE &original, bslma::Allocator *allocator)
Definition bslalg_scalarprimitives.h:1599
Next, we implement three overloads of Imp::copyConstruct
, each taking a different trait specialization. A fourth overload takes false_type
instead of a trait specialization, for those types that don't match any traits. For testing purposes, in addition to copying the data member, each overload also increments a separate counter. These implementations are slightly simplified for readability:
struct Imp {
static int s_noTraitsCounter;
static int s_usesBslmaAllocatorCounter;
static int s_isPairCounter;
static int s_isBitwiseCopyableCounter;
static void clearCounters() {
s_noTraitsCounter = 0;
s_usesBslmaAllocatorCounter = 0;
s_isPairCounter = 0;
s_isBitwiseCopyableCounter = 0;
}
template <class TARGET_TYPE>
static void
copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
new (address) TARGET_TYPE(original, allocator);
++s_usesBslmaAllocatorCounter;
}
template <class TARGET_TYPE>
static void
copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
ScalarPrimitives::copyConstruct(&address->first, original.first,
allocator);
ScalarPrimitives::copyConstruct(&address->second, original.second,
allocator);
++s_isPairCounter;
}
template <class TARGET_TYPE>
static void
copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
memcpy(address, &original, sizeof(original));
++s_isBitwiseCopyableCounter;
}
template <class TARGET_TYPE>
static void
copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
new (address) TARGET_TYPE(original);
++s_noTraitsCounter;
}
};
int Imp::s_noTraitsCounter = 0;
int Imp::s_usesBslmaAllocatorCounter = 0;
int Imp::s_isPairCounter = 0;
int Imp::s_isBitwiseCopyableCounter = 0;
Definition bslmf_selecttrait.h:438
Then, we implement ScalarPrimitives::copyConstruct
:
template <class TARGET_TYPE>
inline void
ScalarPrimitives::copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
{
We use bslmf::SelectTrait
to declare Selection
as a specialization of the first match of the specified traits:
UsesBslmaAllocator,
IsBitwiseCopyable,
IsPair>::Type Selection;
Definition bslmf_selecttrait.h:522
Now, we use Selection
to choose (at compile time), one of the Imp::copyConstruct
overloads defined above:
Imp::copyConstruct(address, original, allocator, Selection());
}
}
Finally, we define three classes, associated with each of the three traits of interest, a fourth class associated with more than one trait (to show that the selection mechanism respects preference) and a fifth class that is not associated with any trait.
The first class is associated with the UsesBslmaAllocator
trait:
class TypeWithAllocator {
int d_value;
public:
: d_value(v), d_alloc(a) { }
TypeWithAllocator(const TypeWithAllocator& other,
: d_value(other.d_value), d_alloc(a) { }
int value() const { return d_value; }
};
template <> struct UsesBslmaAllocator<TypeWithAllocator>
The second class is associated with the IsBitwiseCopyable
trait:
class BitwiseCopyableType {
int d_value;
public:
BitwiseCopyableType(int v = 0) : d_value(v) { }
int value() const { return d_value; }
};
template <> struct IsBitwiseCopyable<BitwiseCopyableType>
The third class is associated with the IsPair
trait:
struct PairType {
TypeWithAllocator first;
BitwiseCopyableType second;
PairType(int a, int b) : first(a), second(b) { }
};
The fourth class is associated with both the IsPair
and IsBitwiseCopyable
traits:
struct BitwiseCopyablePairType {
BitwiseCopyableType first;
BitwiseCopyableType second;
BitwiseCopyablePairType(int a, int b) : first(a), second(b) { }
};
template <>
struct IsPair<BitwiseCopyablePairType> :
bsl::true_type { };
template <> struct IsBitwiseCopyable<BitwiseCopyablePairType>
The fifth class is not associated with any explicit traits:
class TypeWithNoTraits {
int d_value;
public:
TypeWithNoTraits(int v = 0) : d_value(v) { }
int value() const { return d_value; }
};
We use these classes to instantiate ScalarPrimitives::copyConstruct
and verify that the most efficient copy operation that is valid for each type is applied:
int usageExample1()
{
using bslalg::Imp;
void *buffer[4];
char dummy[2];
When we call ScalarPrimitives::copyConstruct
for an object of TypeWithAllocator
, we expect that the copy will have the same value but a different allocator than the original and that the UsesBslmaAllocator
copy implementation will be called once:
Imp::clearCounters();
TypeWithAllocator twa(1, a1);
TypeWithAllocator *twaptr = (TypeWithAllocator*) buffer;
assert(1 == Imp::s_usesBslmaAllocatorCounter);
assert(1 == twaptr->value());
assert(a2 == twaptr->allocator());
twaptr->~TypeWithAllocator();
When we call ScalarPrimitives::copyConstruct
for an object of BitwiseCopyableType
, we expect that the IsBitwiseCopyable
copy implementation will be called once:
Imp::clearCounters();
BitwiseCopyableType bct(2);
BitwiseCopyableType *bctptr = (BitwiseCopyableType*) buffer;
assert(1 == Imp::s_isBitwiseCopyableCounter);
assert(2 == bctptr->value());
bctptr->~BitwiseCopyableType();
When we call ScalarPrimitives::copyConstruct
for an object of PairType
, we expect that the IsPair
copy implementation will be called once for the pair as whole and that the UsesBslmaAllocator
and IsBitwiseCopyable
implementations will be called for the first
and second
members, respectively:
Imp::clearCounters();
PairType pt(3, 4);
PairType *ptptr = (PairType*) buffer;
assert(1 == Imp::s_isPairCounter);
assert(1 == Imp::s_usesBslmaAllocatorCounter);
assert(1 == Imp::s_usesBslmaAllocatorCounter);
assert(3 == ptptr->first.value());
assert(a2 == ptptr->first.allocator());
assert(4 == ptptr->second.value());
ptptr->~PairType();
When we call ScalarPrimitives::copyConstruct
for an object of BitwiseCopyablePairType
, the IsBitwiseCopyable
trait takes precedence over the IsPair
trait (because it appears first in the list of traits used to instantiate SelectTrait
). Therefore, we expect to see the IsBitwiseCopyable
copy implementation called once for the whole pair and the IsPair
copy implementation not called at all:
Imp::clearCounters();
BitwiseCopyablePairType bcpt(5, 6);
BitwiseCopyablePairType *bcptbcptr = (BitwiseCopyablePairType*) buffer;
assert(1 == Imp::s_isBitwiseCopyableCounter);
assert(0 == Imp::s_isPairCounter);
assert(5 == bcptbcptr->first.value());
assert(6 == bcptbcptr->second.value());
bcptbcptr->~BitwiseCopyablePairType();
When we call ScalarPrimitives::copyConstruct
for an object of TypeWithNoTraits
, we expect none of the specialized copy implementations to be called, thus defaulting to the false_type
copy implementation:
Imp::clearCounters();
TypeWithNoTraits twnt(7);
TypeWithNoTraits *twntptr = (TypeWithNoTraits*) buffer;
assert(1 == Imp::s_noTraitsCounter);
assert(7 == twntptr->value());
twntptr->~TypeWithNoTraits();
return 0;
}
Note that using SelectTraits
for dispatching using overloading imposes little or no overhead, since the compiler typically generates no code for the constructor or copy constructor of the trait argument to the overloaded functions. When inlining is in effect, the result is very efficient.