Boost.Parameter:与 CRTP 组合的命名模板参数

Boost.Parameter: named template argument in combination with CRTP

本文关键字:参数 组合 Parameter CRTP Boost      更新时间:2023-10-16

警告:需要提前冗长的介绍来解释问题。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_isPolicy2_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"的参数

:可以关闭此静态检查吗?是通过宏还是模板技巧?

至于可能的解决方法:

  1. 上面的代码在功能上等同于这个手写的法典。对于该代码,CRTP 模式确实有效。然而,它需要大量的样板代码,Boost.Parameter 库如此方便自动化。
  2. 我可以要求 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<>模板。

使用该策略时,可以检查它是否已包装,然后将其解开包装。