Provide a utility to set up SFINAE conditions in type deduction.
More...
Detailed Description
- Outline
-
-
- Purpose:
- Provide a utility to set up SFINAE conditions in type deduction.
-
- Classes:
bsl::enable_if | standard meta-function to drop templates from overload sets |
bsl::enable_if_t | alias to the return type of the meta-function |
bslmf::EnableIf | meta-function to drop templates from overload sets |
-
- Description:
- This component defines two meta-functions,
bsl::enable_if
and bslmf::EnableIf
, both of which may be used to conditionally remove (potential) template instantiations as candidates for overload resolution by causing a deduced template instantiation to fail in a way compatible with the C++ SFINAE rules.
bsl::enable_if
meets the requirements of the enable_if
template defined in the C++11 standard [meta.trans.ptr], while bslmf::EnableIf
was devised before enable_if
was standardized.
- The two meta-functions provide identical functionality. Both meta-functions provide a
typedef
type
that is an alias to a (template parameter) type if a (template parameter) condition is true
; otherwise, type
is not provided.
- Note that
bsl::enable_if
should be preferred over bslmf::EnableIf
, and in general, should be used by new components.
-
- Visual Studio Workaround:
- Because of a Visual Studio bug, described here: http://connect.microsoft.com/VisualStudio/feedback/details/332179/ the Microsoft Visual Studio compiler may not correctly associate a function declaration that uses
bsl::enable_if
with that function's definition, if the definition is not inline to the declaration. This bug affects at least Visual Studio 2008 and 2010. The workaround is to implement functions using bsl::enable_if
inline with their declaration.
-
- Usage:
- The following snippets of code illustrate basic use of the
bsl::enable_if
meta-function. We will demonstrate how to use this utility to control overload sets with three increasingly complex examples.
-
- Example 1: Implementing a Simple Function with bsl::enable_if:
- Suppose that we want to implement a simple
swap
function template to exchange two arbitrary values, as if defined below: template<class t_TYPE>
void DummySwap(t_TYPE& a, t_TYPE& b)
{
t_TYPE temp(a);
a = b;
b = temp;
}
However, we want to take advantage of member-swap methods supplied by user- defined types, so we define a trait that can be customized by a class implementer to indicate that their class supports an optimized member-swap method: template<class t_TYPE>
struct HasMemberSwap : bsl::false_type {
};
Now, we implement a generic swap
function template that will invoke the member swap operation for any type that specialized our trait. The use of bsl::enable_if
to declare the result type causes an attempt to deduce the type t_TYPE
to fail unless the specified condition is true
, and this falls under the "Substitution Failure Is Not An Error" (SFINAE) clause of the C++ standard, so the compiler will look for a more suitable overload rather than fail with an error. Note that we provide two overloaded declarations that appear to differ only in their return type, which would normally raise an ambiguity error. This works, and is in fact required, in this case as the "enable-if" conditions are mutually exclusive, so that only one overload will ever be present in an overload set. Also note that the type
typedef
of bsl::enable_if
is an alias to void
when the (template parameter) type is unspecified and the (template parameter) condition value is true
. Next, we define a simple container template, that supports an optimized swap
operation by merely swapping the internal pointer to the array of elements rather than exchanging each element: template<class t_TYPE>
class MyContainer {
t_TYPE *d_storage;
size_t d_length;
MyContainer(const MyContainer&);
MyContainer& operator=(const MyContainer&);
public:
MyContainer(const t_TYPE& value, int n);
~MyContainer();
void swap(MyContainer &other);
const t_TYPE& front() const;
size_t size() const;
};
Then, we specialize our HasMemberSwap
trait for this new container type. template<class t_TYPE>
struct HasMemberSwap<MyContainer<t_TYPE> > : bsl::true_type {
};
Next, we implement the methods of this class:
template<class t_TYPE>
MyContainer<t_TYPE>::MyContainer(const t_TYPE& value, int n)
: d_storage(new t_TYPE[n])
, d_length(n)
{
for (int i = 0; i != n; ++i) {
d_storage[i] = value;
}
}
template<class t_TYPE>
MyContainer<t_TYPE>::~MyContainer()
{
delete[] d_storage;
}
template<class t_TYPE>
void MyContainer<t_TYPE>::swap(MyContainer& other)
{
::swap(d_storage, other.d_storage);
::swap(d_length, other.d_length);
}
template<class t_TYPE>
const t_TYPE& MyContainer<t_TYPE>::front() const
{
return d_storage[0];
}
template<class t_TYPE>
size_t MyContainer<t_TYPE>::size() const
{
return d_length;
}
Finally, we can test that the member-'swap' method is called by the generic swap
function. Note that the following code will not compile unless the member-function swap
is used, as the copy constructor and assignment operator for the MyContainer
class template are declared as private
. void TestSwap()
{
MyContainer<int> x(3, 14);
MyContainer<int> y(2, 78);
assert(14 == x.size());
assert( 3 == x.front());
assert(78 == y.size());
assert( 2 == y.front());
swap(x, y);
assert(78 == x.size());
assert( 2 == x.front());
assert(14 == y.size());
assert( 3 == y.front());
}
-
- Example 2: Using the bsl::enable_if Result Type:
- For the next example, we will demonstrate the use of the second template parameter in the
bsl::enable_if
template, which serves as the "result" type if the test condition passes. Suppose that we want to write a generic function to allow us to cast between pointers of different types. If the types are polymorphic, we can use dynamic_cast
to potentially cast between two seemingly unrelated types. However, if either type is not polymorphic then the attempt to use dynamic_cast
would be a compile-time failure, and we must use static_cast
instead. #ifdef BSLS_COMPILERFEATURES_SUPPORT_ALIAS_TEMPLATES
Note that if the current compiler supports alias templates C++11 feature, we can use bsl::enable_if_t
alias to the "result" type of bsl::enable_if
meta-function, that avoids the ::type
suffix and typename
prefix in the declaration of the function return type: Next, we define a small number of classes to demonstrate that this casting utility works correctly: class A {
public:
~A() {}
};
class B {
public:
virtual ~B() {}
};
class C {
public:
virtual ~C() {}
};
class ABC : public A, public B, public C {
};
Finally, we demonstrate the correct behavior of the smart_cast
utility: void TestSmartCast()
{
ABC object;
ABC *pABC = &object;
A *pA = &object;
B *pB = &object;
C *pC = &object;
A *pA2 = smart_cast<A>(pABC);
B *pB2 = smart_cast<B>(pC);
C *pC2 = smart_cast<C>(pB);
(void) pA;
assert(&object == pA2);
assert(&object == pB2);
assert(&object == pC2);
}
-
- Example 3: Controlling Constructor Selection with bsl::enable_if:
- The final example demonstrates controlling the selection of a constructor template in a class with (potentially) many constructors. We define a simple container template based on
std::vector
that illustrates a problem that may occur when trying to call the constructor the user expects. For this example, assume we are trying to create a vector<int>
with 42
copies of the value 13
. When we pass the literal values 42
and 13
to the compiler, the "best" candidate constructor should be the template constructor that takes two arguments of the same kind, deducing that type to be int
. Unfortunately, that constructor expects those values to be of an iterator type, forming a valid range. We need to avoid calling this constructor unless the deduced type really is an iterator, otherwise a compile-error will occur trying to instantiate that constructor with an incompatible argument type. We use bsl::enable_if
to create a deduction context where SFINAE can kick in. Note that we cannot deduce the ::type
result of a meta-function, and there is no result type (as with a regular function) to decorate, so we add an extra dummy argument using a pointer type (produced from bsl::enable_if::type
) with a default null argument: template<class t_TYPE>
class MyVector {
t_TYPE *d_storage;
size_t d_length;
MyVector(const MyVector&);
MyVector& operator=(const MyVector&);
public:
MyVector(const t_TYPE& value, int n);
template<class FORWARD_ITERATOR>
MyVector(FORWARD_ITERATOR first, FORWARD_ITERATOR last,
typename bsl::enable_if<
!bsl::is_fundamental<FORWARD_ITERATOR>::value
>::type * = 0)
{
d_length = 0;
for (FORWARD_ITERATOR cursor = first; cursor != last; ++cursor) {
++d_length;
}
d_storage = new t_TYPE[d_length];
for (size_t i = 0; i != d_length; ++i) {
d_storage[i] = *first;
++first;
}
}
~MyVector();
const t_TYPE& operator[](int index) const;
size_t size() const;
};
Note that there is no easy test for whether a type is an iterator, so we assume that any attempt to call a constructor with two arguments that are not fundamental (such as int
) must be passing iterators. Now that we have defined the class template, we implement its methods: template<class t_TYPE>
MyVector<t_TYPE>::MyVector(const t_TYPE& value, int n)
: d_storage(new t_TYPE[n])
, d_length(n)
{
for (int i = 0; i != n; ++i) {
d_storage[i] = value;
}
}
template<class t_TYPE>
MyVector<t_TYPE>::~MyVector()
{
delete[] d_storage;
}
template<class t_TYPE>
const t_TYPE& MyVector<t_TYPE>::operator[](int index) const
{
return d_storage[index];
}
template<class t_TYPE>
size_t MyVector<t_TYPE>::size() const
{
return d_length;
}
Finally, we demonstrate that the correct constructors are called when invoked with appropriate arguments: void TestContainerConstructor()
{
const unsigned int TEST_DATA[] = { 1, 2, 3, 4, 5 };
const MyVector<unsigned int> x(&TEST_DATA[0], &TEST_DATA[5]);
const MyVector<unsigned int> y(13, 42);
assert(5 == x.size());
for (int i = 0; i != 5; ++i) {
assert(TEST_DATA[i] == x[i]);
}
assert(42 == y.size());
for (int i = 0; i != 42; ++i) {
assert(13 == y[i]);
}
}