📄 typeselection.sgml
字号:
<!-- ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| section -->
<section id="typeselection">
<title>Conditional type selection</>
<para>
Conditional type selection is the simplest basic construct of &Cxx; template metaprogramming. Veldhuizen <citation><xref linkend="ref.Vel95a"></> was the first to show how to implement it, and Czarnecki and Eisenecker <citation><xref linkend="ref.CE00"></> first presented it as a standalone library primitive. The &MPL; defines the corresponding facility as follows:
</>
<programlisting>
<![RCDATA[
template<
typename Condition
, typename T1
, typename T2
>
struct if_
{
typedef &unspec; type;
};
]]>
</>
<para>
Note that the first template parameter of the template is a type.
</>
<programlisting>
<![CDATA[
// usage/semantics
typedef mpl::if_<mpl::true_c,char,long>::type t1;
typedef mpl::if_<mpl::false_c,char,long>::type t2;
BOOST_MPL_ASSERT_IS_SAME(t1, char);
BOOST_MPL_ASSERT_IS_SAME(t2, long);
]]>
</>
<para>
The construct is important because template metaprograms often contain a lot of decision-making code, and, as we will show, spelling it manually every time via (partial) class template specialization quickly becomes impractical. The template is also important from the point of encapsulating the compiler workarounds.
</>
<!-- ||||||||||||||||||||||||||||| subsection -->
<section id="delayedeval">
<title>Delayed evaluation</>
<para>
The way the &Cxx; template instantiation mechanism works imposes some subtle limitations on applicability of the type selection primitive (<literal>if_</>), compared to a manually implemented equivalent of the selection code. For example, suppose we are implementing a <literal>pointed_type</> traits template such that <literal>pointed_type<T>::type</> instantiated for a <literal>T</> that is either a plain pointer (<literal>U*</>), <literal>std::auto_ptr<U></>, or any of the Boost smart pointers <citation><xref linkend="ref.SPL"></>, e.g. <literal>boost::scoped_ptr<U></>, will give us the pointed type (<literal>U</>):
</>
<programlisting>
<![CDATA[
BOOST_MPL_ASSERT_IS_SAME(pointed_type<my*>::type, my);
BOOST_MPL_ASSERT_IS_SAME(pointed_type< std::auto_ptr<my> >::type, my);
BOOST_MPL_ASSERT_IS_SAME(pointed_type< boost::scoped_ptr<my> >::type, my);
]]>
</>
<para>
Unfortunately, the straightforward application of <literal>if_</> to this problem does not work:
<footnote id="note.pointedtype"><para>
Although it would be easy to implement <literal>pointed_type</> using partial specialization to distinguish the case where <literal>T</> is a pointer, <literal>if_</> is likely to be the right tool for dealing with more complex conditionals. For the purposes of exposition, please suspend disbelief!
</></>
</>
<programlisting>
<![CDATA[
template< typename T >
struct pointed_type
: mpl::if_<
boost::is_pointer<T>
, typename boost::remove_pointer<T>::type
, typename T::element_type // #1
>
{
};
// the following code causes compilation error in line #1:
// name followed by "::" must be a class or namespace name
typedef pointed_type<char*>::type result;
]]>
</>
<para>
Clearly, the expression <literal>typename T::element_type</> is not valid in the case of <literal>T == char*</>, and that's what the compiler is complaining about. Implementing the selection code manually solves the problem:
</>
<programlisting>
<![CDATA[
namespace aux {
// general case
template< typename T, bool is_pointer = false >
struct select_pointed_type
{
typedef typename T::element_type type;
};
// specialization for plain pointers
template< typename T >
struct select_pointed_type<T,true>
{
typedef typename boost::remove_pointer<T>::type type;
};
}
template< typename T >
struct pointed_type
: aux::select_pointed_type<
T, boost::is_pointer<T>::value
>
{
};
]]>
</>
<para>
But this quickly becomes awkward if needs to be done repeatedly, and this awkwardness is compounded when partial specialization is not available. We can try to work around the problem as follows:
</>
<programlisting>
<![CDATA[
namespace aux {
template< typename T >
struct element_type
{
typedef typename T::element_type type;
};
}
template< typename T >
struct pointed_type
{
typedef typename mpl::if_<
boost::is_pointer<T>
, typename boost::remove_pointer<T>::type
, typename aux::element_type<T>::type
>::type type;
};
]]>
</>
<para>
but this doesn't work either - the access to the <literal>aux::element_type<T></>'s nested <literal>type</> member still forces the compiler to instantiate <literal>element_type<T></> with <literal>T == char*</>, and that instantiation is, of course, invalid. Also, although in our case this does not lead to a compile error, the <literal>boost::remove_pointer<T></> template always gets instantiated as well, and for the same reason (because we are accessing its nested <literal>type</> member). Unnecessary instantiation that is not fatal may or may be not a problem, depending on the <quote>weight</> of the template (how much the instantiation taxes the compiler), but a general rule of thumb would be to avoid such code.
</>
<para>
Returning to our error, to make the above code compile, we need to factor the act of <quote>asking</> <literal>aux::element_type<T></> for its nested <literal>type</> out of the <literal>if_</> invocation. The fact that both the <literal>boost::remove_pointer<T></> trait template and <literal>aux::element_type<T></> use the same naming convention for their result types makes the refactoring easier:
</>
<programlisting>
<![CDATA[
template< typename T >
struct pointed_type
{
private:
typedef typename mpl::if_<
boost::is_pointer<T>
, boost::remove_pointer<T>
, aux::element_type<T>
>::type func_;
public:
typedef typename func_::type type;
};
]]>
</>
<para>
Now the compiler is guaranteed not to instantiate both <literal>boost::remove_pointer<T></> and <literal>aux::element_type<T></>, even although they are used as actual parameters to the <literal>if_</> template, so we are allowed to get away with <literal>aux::element_type<char*></> so long as it won't end up being selected as <literal>func_</>.
</>
<para>
The above technique is so common in template metaprograms, that it even makes sense to facilitate the selection of a nested <literal>type</> member by introducing a high-level equivalent to <literal>if_</> - the one that will do the <literal>func_::type</> operation (that is called [nullary] metafunction class application) as a part of its invocation. The &MPL; provides such template - it's called <literal>apply_if</>. Using it, we can re-write the above code as simple as:
</>
<programlisting>
<![CDATA[
template< typename T >
struct pointed_type
{
typedef typename mpl::apply_if<
boost::is_pointer<T>
, boost::remove_pointer<T>
, aux::element_type<T>
>::type type;
};
]]>
</>
<para>
To make our techniques review complete, let's consider a slightly different example - suppose we want to define a high-level wrapper around <literal>boost::remove_pointer</> traits template <citation><xref linkend="ref.TTL"></>, which will strip the pointer qualification conditionally. We will call it <literal>remove_pointer_if</>:
</>
<programlisting>
<![CDATA[
template<
typename Condition
, typename T
>
struct remove_pointer_if
{
typedef typename mpl::if_<
Condition
, typename boost::remove_pointer<T>::type
, T
>::type type;
};
]]>
</>
<para>
Now the above works the first time, but it suffers from the problem we mentioned earlier - <literal>boost::remove_pointer<T></> gets instantiated even if its result is never used. In the metaprogramming world compilation time is an important resource <citation><xref linkend="ref.Abr01"></>, and it is wasted by unnecessary template instantiations. We've just seen how to deal with the problem when both arguments to <literal>if_</> are the results of nullary metafunction class applications, but in this example one of the arguments (<literal>T</>) is just a simple type, so the refactoring just doesn't seem possible.
</>
<para>
The easiest way out of this situation would be to pass to <literal>if_</> a real nullary metafunction instead of <literal>T</> - the one that returns <literal>T</> on its invocation. The &MPL; provides a simple way to do it - we just substitute <literal>identity<T></> and <literal>apply_if</> for <literal>T</> and <literal>if_</>:
</>
<programlisting>
<![CDATA[
template<
typename Condition
, typename T
>
struct remove_pointer_if
{
typedef typename mpl::apply_if<
Condition
, boost::remove_pointer<T>
, mpl::identity<T>
>::type type;
};
]]>
</>
<para>
which gives us exactly what we wanted.
</>
</section>
</section>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -