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

Detailed Description

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,
bslma::Allocator *allocator)
{
BSLS_ASSERT_SAFE(address);
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:

template <class t_TYPE> struct UsesBslmaAllocator : bsl::false_type { };
template <class t_TYPE> struct IsBitwiseCopyable : bsl::false_type { };
template <class t_TYPE> struct IsPair : bsl::false_type { };

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; }
namespace bslalg {
struct ScalarPrimitives {
template <class TARGET_TYPE>
static void copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
bslma::Allocator *allocator);
};
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 {
// Counters for counting overload calls
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,
bslma::Allocator *allocator,
{
new (address) TARGET_TYPE(original, allocator);
++s_usesBslmaAllocatorCounter;
}
template <class TARGET_TYPE>
static void
copyConstruct(TARGET_TYPE *address,
const TARGET_TYPE& original,
bslma::Allocator *allocator,
{
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,
bslma::Allocator *allocator)
{

We use bslmf::SelectTrait to declare Selection as a specialization of the first match of the specified traits:

typedef typename bslmf::SelectTrait<TARGET_TYPE,
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());
} // end copyConstruct()
} // Close namespace bslalg

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;
bslma::Allocator *d_alloc;
public:
TypeWithAllocator(int v = 0, bslma::Allocator *a = 0) // IMPLICIT
: d_value(v), d_alloc(a) { }
TypeWithAllocator(const TypeWithAllocator& other,
: d_value(other.d_value), d_alloc(a) { }
int value() const { return d_value; }
bslma::Allocator *allocator() const { return d_alloc; }
};
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) { } // IMPLICIT
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) { }
};
template <> struct IsPair<PairType> : bsl::true_type { };

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) { } // IMPLICIT
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;
// This buffer is properly aligned and big enough to hold any of the
// test types.
void *buffer[4];
char dummy[2]; // Dummy addresses
bslma::Allocator *a1 = (bslma::Allocator*) &dummy[0];
bslma::Allocator *a2 = (bslma::Allocator*) &dummy[1];

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;
// Prefer IsBitwiseCopyable over IsPair trait
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.