Coercion by Member Template
一、"coercion"的意思是"强迫、强制"。
二、使用了这个idiom的案例包括
1、std::unique_ptr
2、microsoft/GSL/pointers class not_null
三、提供了一个非模板版本 和 模板版本
More C++ Idioms/Coercion by Member Template
Intent
To increase the flexibility of a class template's interface by allowing the class template to participate in the same implicit type conversions (coercion) as its parameterizing types enjoy.
Motivation
It is often useful to extend a relationship between two types to class templates specialized with those types.
NOTE: 目的: 将两个type之间的relationship推广到"class templates specialized with those types"。
For example, suppose that class D
derives from class B
. A pointer to an object of type D
can be assigned to a pointer to B;
C++ supports that implicitly. However, types composed of these types do not share the relationship of the composed types. That applies to class templates as well, so a Helper<D>
object normally cannot be assigned to a Helper<B>
object.
class B
{
};
class D: public B
{
};
template<class T>
class Helper
{
};
int main()
{
B *bptr;
D *dptr;
bptr = dptr; // OK; permitted by C++
Helper<B> hb;
Helper<D> hd;
hb = hd; // Not allowed but could be very useful
}
NOTE: 上述程序编译报错如下:
test.cpp:14:4: error: no match for ‘operator=’ (operand types are ‘Helper<B>’ and ‘Helper<D>’)
即没有
operator=
,则无法进行conversion。
There are cases where such conversions are useful, such as allowing conversion from std::unique_ptr<D>
to std::unique_ptr<B>
. That is quite intuitive, but isn't supported without using the Coercion by Member Template Idiom.
Solution and Sample Code
Define member template functions, in a class template, which rely on the implicit type conversions supported by the parameter types. In the following example, the templated constructor and assignment operator work for any type U
, for which initialization or assignment of a T *
from a U *
is allowed.
#include<iostream>
using namespace std;
class B
{
};
class D: public B
{
};
template<class T>
class Ptr
{
public:
Ptr() :
ptr(NULL)
{
}
Ptr(Ptr const &p) :
ptr(p.ptr)
{
std::cout << "Copy constructor\n";
}
// Supporting coercion using member template constructor.
// This is not a copy constructor, but behaves similarly.
template<class U>
Ptr(Ptr<U> const &p) :
ptr(p.ptr) // Implicit conversion from U to T required
{
std::cout << "Coercing member template constructor\n";
}
// Copy assignment operator.
Ptr& operator =(Ptr const &p)
{
ptr = p.ptr;
std::cout << "Copy assignment operator\n";
return *this;
}
// Supporting coercion using member template assignment operator.
// This is not the copy assignment operator, but works similarly.
template<class U>
Ptr& operator =(Ptr<U> const &p)
{
ptr = p.ptr; // Implicit conversion from U to T required
std::cout << "Coercing member template assignment operator\n";
return *this;
}
T *ptr;
};
int main(void)
{
Ptr<D> d_ptr;
Ptr<B> b_ptr(d_ptr); // Now supported
b_ptr = d_ptr; // Now supported
}
// g++ test.cpp --std=c++03
NOTE:
输出如下:
Coercing member template constructor Coercing member template assignment operator
Another use for this idiom is to permit assigning an array of pointers to a class to an array of pointers to that class' base. Given that D
derives from B
, a D
object is-a B
object. However, an array of D
objects is-not-an array of B
objects. This is prohibited in C++ because of slicing. Relaxing this rule for an array of pointers can be helpful. For example, an array of pointers to D
should be assignable to an array of pointers to B
(assuming B
's destructor is virtual). Applying this idiom can achieve that, but extra care is needed to prevent copying arrays of pointers to one type to arrays of pointers to a derived type. Specializations of the member function templates or SFINAE can be used to achieve that.
NOTE:
1、"assigning an array of pointers to a class to an array of pointers to that class' base" 让我想起了:
type covariance
The following example uses a templated constructor and assignment operator expecting Array<U *>
to only allow copying Arrays of pointers when the element types differ.
#include<iostream>
#include<algorithm>
class B
{
};
class D: public B
{
};
template<class T>
class Array
{
public:
Array()
{
}
Array(Array const &a)
{
std::copy(a.array_, a.array_ + SIZE, array_);
}
template<class U>
Array(Array<U*> const &a)
{
std::copy(a.array_, a.array_ + SIZE, array_);
}
template<class U>
Array& operator =(Array<U*> const &a)
{
std::copy(a.array_, a.array_ + SIZE, array_);
}
enum
{
SIZE = 10
};
T array_[SIZE];
};
// g++ test.cpp --std=c++03
Many smart pointers such as std::unique_ptr
, std::shared_ptr
employ this idiom.
Caveats
A typical mistake in implementing the Coercion by Member Template Idiom is failing to provide the non-template copy constructor or copy assignment operator when introducing the templated copy constructor and assignment operator. A compiler will automatically declare a copy constructor and a copy assignment operator if a class does not declare them, which can cause hidden and non-obvious faults when using this idiom.
NOTE: