警告:需要提前冗长的介绍来解释问题。Vandevoorde 和 Josuttis 的第 16.1 章中首次描述的命名模板参数习语可以用 Boost.Parameter 库方便地编写
#include <iostream>
#include <typeinfo>
#include <boost/parameter.hpp>
#include <boost/static_assert.hpp>
struct DefaultPolicy1 {};
struct DefaultPolicy2 {};
typedef boost::parameter::void_ DefaultSetter;
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy1_is)
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy2_is)
typedef boost::parameter::parameters<
boost::parameter::optional<tag::Policy1_is>,
boost::parameter::optional<tag::Policy2_is>
> PolicySelector;
template
<
class PolicySetter1 = DefaultSetter,
class PolicySetter2 = DefaultSetter
>
class BreadSlicer
{
typedef typename PolicySelector::bind<
PolicySetter1,
PolicySetter2
>::type Policies;
public:
// extract policies:
typedef typename boost::parameter::value_type<
Policies, tag::Policy1_is, DefaultPolicy1
>::type P1;
typedef typename boost::parameter::value_type<
Policies, tag::Policy2_is, DefaultPolicy2
>::type P2;
};
上面的代码允许通过将它们命名为Policy1_is
和Policy2_is
来覆盖BreadSlicer
的可选模板参数。这使得使用许多默认参数的基于策略的设计非常方便。
int main()
{
typedef BreadSlicer<> B1;
// can override any default policy
typedef BreadSlicer< Policy1_is<int> > B2;
typedef BreadSlicer< Policy2_is<char> > B3;
// order of policy-setting is irrelevant
typedef BreadSlicer< Policy1_is<int>, Policy2_is<char> > B4;
typedef BreadSlicer< Policy2_is<char>, Policy1_is<int> > B5;
// similar static asserts work for B1 ... B4
BOOST_STATIC_ASSERT((std::is_same<B5::P1, int >::value));
BOOST_STATIC_ASSERT((std::is_same<B5::P2, char>::value));
return 0;
}
为了避免基于策略的设计中非常微妙的ODR违规(有关解释,请参阅Alexandrescu的这篇旧文章),我希望能够在命名模板参数上应用CRTP模式:
int main()
{
// ERROR: this code does NOT compile!
struct CuriousBreadSlicer
:
BreadSlicer< Policy1_is<CuriousBreadSlicer> >
{};
typedef CuriousBreadSlicer B6;
BOOST_STATIC_ASSERT((std::is_same<B6::P1, CuriousBreadSlicer>::value));
BOOST_STATIC_ASSERT((std::is_same<B6::P2, DefaultPolicy2 >::value));
return 0;
}
但是,上面的 Boost.Parameter 实现无法编译,因为某些内部static_assert失败并显示类似 (VC10 SP1) 的消息
'main::CuriousBreadSlicer' : 不允许将未定义的类作为 编译器内部类型特征"__is_base_of"的参数
问:可以关闭此静态检查吗?是通过宏还是模板技巧?
至于可能的解决方法:
- 上面的代码在功能上等同于这个手写的法典。对于该代码,CRTP 模式确实有效。然而,它需要大量的样板代码,Boost.Parameter 库如此方便自动化。
- 我可以要求 CRTP 参数始终在模板列表中排在第一位参数,而不是将其包装在
Policy1_is
类中。这解决了编译时错误,但它失去了覆盖的顺序独立性。
所以看起来我就是高尔夫球手所说的"在俱乐部之间"。哪种解决方案最好?
一个没有CRTP的最小示例:
#include <boost/parameter.hpp>
#include <boost/static_assert.hpp>
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy1_is)
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy2_is)
typedef boost::parameter::parameters<
boost::parameter::optional<tag::Policy1_is>,
boost::parameter::optional<tag::Policy2_is>
> PolicySelector;
struct foo {};
struct bar {};
struct baz;
typedef typename PolicySelector::bind<foo, baz>::type Policies;
boost::parameter::value_type<Policies, tag::Policy1_is, bar>::type x; // <- !!!
因此boost::parameter::value_type
要求策略选择器基于完整类型,而手写类的情况并非如此。
我不完全确定为什么一个类需要自己作为自己的策略。如果你需要它,也许你可以将一个不完整的类型包装在完整的东西中:
struct CuriousBreadSlicer : BreadSlicer <
Policy1_is<CuriousBreadSlicer *> > // <- compiles
或者,为清楚起见,您可以使用自己的wrap_incomplete_type<>
模板。
使用该策略时,可以检查它是否已包装,然后将其解开包装。