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

Detailed Description

Outline

Purpose

Provide a facility for defining traits and detecting legacy traits.

Classes

See also
bslmf_nestedtraitdeclaration

Description

This component defines a meta-function, bslmf::DetectNestedTrait, that facilitates the creation of traits that can be associated with a type using the nested trait mechanism in bslmf_declarenestedtrait . Such traits are referred to as "nested traits" because their association with a type is embedded within the type's definition. bslmf::DetectNestedTrait and can also be used to detect traits created using older legacy traits definitions mechanisms used at Bloomberg.

Please note:

  1. The use of bslmf::DetectNestedTrait to detect traits is deprecated. Clients should detect traits using the C++11 idiom (see {Nested Trait Idiom vs. C++11 Trait Idiom} below)
  2. Clients are encouraged to use a C++11 idiom for defining traits. However, authors of traits who want trait users to be able to take advantage of bslmf_nestedtraitdeclaration must define traits that inherit from bslmf::DetectNestedTrait (see {Writing a User-Defined Trait} below).

Nested Trait Idiom vs. C++11 Trait Idiom

BDE supports two idioms for defining traits and associating them with types. The older idiom uses bslmf::DetectNestedTrait to define traits, and the BSLMF_NESTED_TRAIT_DECLARATION* macros to associate traits with types. This idiom is called the "nested trait" idiom.

The newer idiom is familiar to users of C++11 traits, and is referred to here as the "C++11 trait" idiom. In the C++11 trait idiom, a trait is a template that derives its truth value from bsl::true_type or bsl::false_type, and is associated with a type by providing a specialization of the trait for the associated type.

For example, a minimal C++11 trait, abcd::C11Trait, could be defined as:

namespace abcd {
template <class t_TYPE>
struct C11Trait : bsl::false_type {
};
} // close namespace abcd
Definition bslmf_integralconstant.h:244

abcd::C11Trait would then be associated with a class, xyza::SomeClass, by specializing the trait for that class:

namespace xyza {
class SomeClass {
// The definition of 'SomeClass' does not affect the trait mechanism.
// ...
};
} // close namespace xyza
namespace abcd {
template <>
struct C11Trait<xyza::SomeClass> : bsl::true_type {
};
} // close namespace abcd

Note that the specialization is defined in the same namespace as the original trait.

Both idioms detect the association of a trait with a class in the same way: by inspecting the trait's value member.

assert(true == abcd::C11Trait<xyza::SomeClass>::value);
assert(false == abcd::C11Trait<xyza::Foo>::value);
assert(true == abcd::BarTrait<xyza::Foo>::value);
assert(false == abcd::BarTrait<xyza::SomeClass>::value);

The C++11 trait idiom is the standard idiom for new code in BDE.

Writing a User-Defined Trait

On systems that do not require compatibility with the nested trait idiom, new traits should be written according to the C++11 trait idiom.

On systems that support the nested trait idiom, any new user-defined trait should derive its truth value from bslmf::DetectNestedTrait following the Curiously Recurring Template Pattern. This will allow the trait to be detected by directly inspecting the trait's value member, regardless of whether the trait is associated with a type through the nested trait idiom or through the C++11 trait idiom.

Therefore, the simplest maximally-compatible trait would look like this:

template <class t_TYPE>
struct MyTrait : bslmf::DetectNestedTrait<t_TYPE, MyTrait>::type {};
Definition bslmf_detectnestedtrait.h:464

A trait having more complex default logic could derive from bsl::integral_constant using the value member of bslmf::DetectNestedTrait, such as:

template <class t_TYPE>
struct ComplexTrait : bsl::integral_constant<bool,
bslmf::DetectNestedTrait<t_TYPE, ComplexTrait>::value
|| SomeOtherTrait<t_TYPE>::value> {
};

These are the only recommended uses of bslmf::DetectNestedTrait<t_TYPE, t_TRAIT>::type and bslmf::DetectNestedTrait<t_TYPE, t_TRAIT>::value.

Detecting Legacy Traits

If a trait, t_TRAIT, has been associated with a type, t_TYPE, using one of the BSLMF_NESTED_TRAIT_DECLARATION* macros then bslmf::DetectNestedTrait<t_TYPE, t_TRAIT> derives from bsl::true_type. Otherwise, bslmf::DetectNestedTrait<t_TYPE, t_TRAIT> derives from bsl::false_type.

Therefore, if a trait abcd::BarTrait has been associated with a class xyza::Foo in the following way:

namespace xyza {
class Foo {
// ... various implementation details ...
public:
// TRAITS
BSLMF_NESTED_TRAIT_DECLARATION(Foo, abcd::BarTrait);
// ... the rest of the public interface ...
};
} // close namespace xyza
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231

then bslmf::DetectNestedTrait<t_TYPE, t_TRAIT>::value will evaluate to true and bslmf::DetectNestedTrait<t_TYPE, t_TRAIT>::type will be bsl::true_type.

Usage

This section illustrates intended use of this component.

Example 1: Defining a Custom Trait

When writing generic infrastructure code, we often need to choose among multiple code paths based on the capabilities of the types on which we are operating. If those capabilities are reflected in a type's public interface, we may be able to use techniques such as SFINAE to choose the appropriate code path. However, SFINAE cannot detect all of a type's capabilities. In particular, SFINAE cannot detect constructors, memory allocation, thread-safety characteristics, and so on. Functions that depend on these capabilities must use another technique to determine the correct code path to use for a given type. We can solve this sort of problem by associating types with custom traits that indicate what capabilities are provided by a given type.

First, in package abcd, define a trait, RequiresLockTrait, that indicates that a type's methods must not be called unless a known lock it first acquired:

namespace abcd {
template <class t_TYPE>
struct RequiresLockTrait :
bslmf::DetectNestedTrait<t_TYPE, RequiresLockTrait>::type {
};
} // close package namespace

Notice that RequiresLockTrait derives from bslmf::DetectNestedTrait<t_TYPE, RequiresLockTrait>::type using the curiously recurring template pattern.

Then, in package xyza, we declare a type, DoesNotRequireALockType, that can be used without acquiring the lock:

namespace xyza {
class DoesNotRequireLockType {
// ...
public:
// CREATORS
DoesNotRequireLockType();
// ...
};

Next, we declare a type, RequiresLockTypeA, that does require the lock. We use the BSLMF_NESTED_TRAIT_DECLARATION macro to associate the type with the abcd::RequiresLockTrait trait:

class RequiresLockTypeA {
// ...
public:
// TRAITS
BSLMF_NESTED_TRAIT_DECLARATION(RequiresLockTypeA,
abcd::RequiresLockTrait);
// CREATORS
RequiresLockTypeA();
// ...
};

Notice that the macro declaration is performed within the scope of the class declaration, and must be done with public scope.

Then, we declare a templatized container type, Container, that is parameterized on some ELEMENT type. If ELEMENT requires a lock, then a Container of ELEMENTs will require a lock as well. This can be expressed using the BSLMF_NESTED_TRAIT_DECLARATION_IF macro, by providing abcd::RequiresLockTrait<ELEMENT>::value as the condition for associating the trait with Container.

template <class ELEMENT>
struct Container {
// ...
public:
// TRAITS
BSLMF_NESTED_TRAIT_DECLARATION_IF(Container, abcd::RequiresLockTrait,
abcd::RequiresLockTrait<ELEMENT>::value);
// ...
};
#define BSLMF_NESTED_TRAIT_DECLARATION_IF(t_TYPE, t_TRAIT, t_COND)
Definition bslmf_nestedtraitdeclaration.h:243

Next, we show that traits based on bslmf::DetectNestedTrait can be associated with a type using "C++11-style" trait association. To do this, we declare a type, RequiresLockTypeB, that also requires the lock, but does not used the BSLMF_NESTED_TRAIT_DECLARATION macro:

class RequiresLockTypeB {
// ...
public:
// CREATORS
RequiresLockTypeB();
// ...
};
} // close package namespace

Then, we associate RequiresLockTypeB with abcd::RequiresLockTrait by directly specializing abcd::RequiresLockTrait<xyza::RequiresLockTypeB>. This is the standard way of associating a type with a trait since C++11:

namespace abcd {
template <>
struct RequiresLockTrait<xyza::RequiresLockTypeB> : bsl::true_type {
};
} // close namespace abcd

Now, we can write a function that inspects abcd::RequiresLockTrait<t_TYPE>::value to test whether or not various types are associated with abcd::RequiresLockTrait:

void example1()
{
assert(false ==
abcd::RequiresLockTrait<xyza::DoesNotRequireLockType>::value);
assert(true ==
abcd::RequiresLockTrait<xyza::RequiresLockTypeA>::value);
assert(true ==
abcd::RequiresLockTrait<xyza::RequiresLockTypeB>::value);
assert(false ==
abcd::RequiresLockTrait<
xyza::Container<xyza::DoesNotRequireLockType> >::value);
assert(true ==
abcd::RequiresLockTrait<
xyza::Container<xyza::RequiresLockTypeA> >::value);
assert(true ==
abcd::RequiresLockTrait<
xyza::Container<xyza::RequiresLockTypeB> >::value);
// ...
}

Finally, we demonstrate that the trait can be tested at compilation time, by writing a function that tests the trait within the context of a compile-time BSLMF_ASSERT:

void example2()
{
BSLMF_ASSERT(false ==
abcd::RequiresLockTrait<xyza::DoesNotRequireLockType>::value);
BSLMF_ASSERT(true ==
abcd::RequiresLockTrait<xyza::RequiresLockTypeA>::value);
BSLMF_ASSERT(true ==
abcd::RequiresLockTrait<xyza::RequiresLockTypeB>::value);
BSLMF_ASSERT(false ==
abcd::RequiresLockTrait<
xyza::Container<xyza::DoesNotRequireLockType> >::value);
BSLMF_ASSERT(true ==
abcd::RequiresLockTrait<
xyza::Container<xyza::RequiresLockTypeA> >::value);
BSLMF_ASSERT(true ==
abcd::RequiresLockTrait<
xyza::Container<xyza::RequiresLockTypeB> >::value);
}
#define BSLMF_ASSERT(expr)
Definition bslmf_assert.h:229