使用奇妙递归模板模式在子类之间进行转换

时间:2014-08-14 09:33:49

标签: c++ templates template-meta-programming crtp

假设:

  • 模板类Base,其typename参数为value_type
  • Curiously Recursive Template Pattern模板“子类”DerivedFooDerivedBar以及
  • 模板“子类”DerivedBarCodeDerivedBarDoge,源自DerivedBar

我怎么可能,或者我可以:

  • 如图所示,在每对三叶派生类型之间实现转换(通过显式构造函数,赋值运算符,),
  • 禁用(通过编译器错误消息)图中未指定的转换?

图表:

Base -+---> DerivedFoo <-------+
      +-+-> DerivedBar         | // Three-way
        +---> DerivedBarCode <-+ // conversion
        +---> DerivedBarDoge <-+

该场景摘自:

MatrixBase -+---> DenseMatrix <------------+
            +-+-> SparseMatrixBase         | 
              +---> MatrixCSR <------------+ // Multiple-way
              +---> MatrixCSC <------------+ // conversion
              +---> MatrixModifiedCSR <----+
              +---> MatrixModifiedCSC <----+

其中每个类至少有一个公共模板参数typename value_type

最低示范性示例:

解释

  • 行116 ... 128 行160 ... 169 显示我尝试定义模板类之外的模板类的显式模板构造函数,以进行转换DerivedBarCode<value_type>DerivedBarDoge<value_type> DerivedFoo<value_type>
  • 第49行包含静态断言,该断言会停止图中未指定的所有转换。
  • 删除第189 ... 202行<202>以及第116行... 128 第160行...... 169 以成功编译运行。

代码:

#include <iostream>

// ----------------------------------------------------------------------------
// Base
// ----------------------------------------------------------------------------
template <typename value_type, typename derived_type>
class Base
{
public:
    // Pass along the value type.
    typedef value_type value_type;
    // Identify myself in the Curiously Recursive Template Pattern hierarchy.
    typedef Base object_type;
    // Delegate to derived types.
    derived_type & asLeaf() { return static_cast<derived_type &>(*this); }

protected:
    value_type m_Base;

public:
    Base(value_type base = value_type()) : m_Base(base) {}

public:
    // A delegated function
    void Dump() { asLeaf().Dump(); }
};

// ----------------------------------------------------------------------------
// Base -----> Derived Foo
// ----------------------------------------------------------------------------
template <typename value_type>
class DerivedFoo : public Base < value_type, DerivedFoo<value_type> >
{
public:
    // Identify myself in the CRTP hierarchy.
    typedef DerivedFoo object_type;

protected:
    value_type m_Foo;

public:
    DerivedFoo(value_type base = value_type(), value_type foo = value_type()) :
        Base < value_type, DerivedFoo<value_type> >(base), m_Foo(foo) {}

    template <typename object_type_2>
    DerivedFoo(object_type_2 const &other)
    {
        static_assert(false, "Non-specialized template constructor disabled.");
    }

public:
    // A possible implementation of the delegated function
    void Dump()
    {
        std::cout << "DerivedFoo = [" << m_Base << ", " << m_Foo << "]\n";
    }
};

// ----------------------------------------------------------------------------
// Base -+---> DerivedFoo -+ Two-way
//       +---> DerivedBar -+ conversion
// ----------------------------------------------------------------------------
template <typename value_type, typename bar_type>
class DerivedBar : public Base < value_type, DerivedBar<value_type, bar_type> >
{
public:
    // Pass along the derived bar type.
    typedef bar_type bar_type;
    // Identify myself in the CRTP hierarchy.
    typedef DerivedBar object_type;
    // Delegate to derived types.
    bar_type & asLeaf() { return static_cast<bar_type &>(*this); }

protected:
    value_type m_Bar;

public:
    DerivedBar(value_type base = value_type(), value_type bar = value_type()) :
        Base < value_type, DerivedBar<value_type, bar_type> >(base), m_Bar(bar)
    {}

public:
    void Dump() { asLeaf().Dump(); }
};

// ----------------------------------------------------------------------------
// Base -+---> DerivedFoo <-------+
//       +-+-> DerivedBar <-------+ Three-way conversion
//         +---> DerivedBarCode <-+
// ----------------------------------------------------------------------------
template <typename value_type>
class DerivedBarCode :
    public DerivedBar < value_type, DerivedBarCode<value_type> >
{
public:
    typedef DerivedBarCode object_type;

protected:
    value_type m_Code = 8;

public:
    DerivedBarCode(value_type base = value_type(),
        value_type bar = value_type(), value_type code = value_type()) :
        DerivedBar < value_type, DerivedBarCode<value_type> >(base, bar),
        m_Code(code) {}

public:
    void Dump()
    {
        std::cout << "DerivedBarCode = ["
            << m_Base << ", " << m_Bar << ", " << m_Code << "]\n";
    }
};

// DerivedBarCode => DerivedFoo
// Example of what I'm trying to do:
template <typename value_type>
template <>
DerivedFoo<value_type>::DerivedFoo(
    typename DerivedBarCode<value_type>::object_type const &other)
{
    m_Base = other.m_Base;
    m_Foo = other.m_Foo;
    // There may be other calculations, e.g. replacing the line above with:
    // m_Foo = other.m_Foo + other.m_Code;
    std::cout << "m_Code = " << other.m_Code << '\n';
}

// ----------------------------------------------------------------------------
// Base -+---> DerivedFoo <-------+
//       +-+-> DerivedBar <-------+ Four-way
//         +---> DerivedBarCode <-+ conversion
//         +---> DerivedBarDoge <-+
// ----------------------------------------------------------------------------
template <typename value_type>
class DerivedBarDoge :
    public DerivedBar < DerivedBarDoge<value_type>, value_type > 
{
public:
    typedef DerivedBarDoge object_type;

protected:
    value_type m_Doge;

public:
    DerivedBarDoge(value_type base = value_type(),
        value_type bar = value_type(), value_type dance = value_type()) :
        DerivedBar < DerivedBarDoge<value_type>, value_type >(base, bar),
        m_Code(dance) {}

    void Dump()
    {
        std::cout << "DerivedBarDoge = ["
            << m_Base << ", " << m_Bar << ", " << m_Doge << "]\n";
    }

};

// DerivedBarDoge => DerivedFoo
// Another attempt.
template <typename value_type>
template <>
DerivedFoo<value_type>::DerivedFoo(DerivedBarDoge<value_type> const &other)
{
    m_Base = other.m_Base;
    m_Foo = other.m_Foo;
    std::cout << "m_Doge = " << other.m_Doge << '\n';
}


int main()
{
    DerivedFoo<double> foo(1.0, 2.0);
    foo.Dump();
    // Output:
    // DerivedFoo = [1, 2];

    DerivedBarCode<double> barcode(4.0, 8.0, 16.0);
    barcode.Dump();
    // Output:
    // DerivedBarCode = [4, 8, 16];

    DerivedBarDoge<double> bardoge(32.0, 64.0, 128.0);
    bardoge.Dump();
    // Output:
    // DerivedBarDoge = [32, 64, 128];

    DerivedFoo<double> converted(barcode);
    converted.Dump();
    // Expected output:
    // DerivedFoo = [4, 8];

    converted = bardoge;
    converted.Dump();
    // Expected output:
    // DerivedFoo = [32, 64];

    barcode = foo;
    barcode.Dump();
    // Expected output:
    // DerivedFoo = [1, 2, 16];

    // system("pause");
    return 0;
}

2 个答案:

答案 0 :(得分:1)

CRTP基础会将其类名注入派生类。因此,如果每个CRTP基础知道派生类型为derived_type,那么到达另一个基础other_base< derived_type >只是一个简单的事情

static_cast< typename derived_type::other_base & >(
            static_cast< derived_type & >( * this ) );

如果CRTP base mixin可以通过几个模板中的任何一个实现,那么它应该为自己的类型添加一个typedef,以识别它是哪个mix。然后将其用于::other_base

要添加转化,只需将此类广告转换为转化运算符模板即可。在实际尝试之前,请务必使用SFINAE清理转换。

template< typename t,
    std::enable_if_t< std::is_base_of_v< t, derived_type > > * = nullptr >
operator t & ()
    { return etc; }

您可以另外/可选地使用成员typedef标记所有合适的mixin,并且SFINAE会检查该标记。要仅选择图表中的过渡,请将标记升级为布尔元函数。

答案 1 :(得分:1)

首先,不要再考虑模板层次结构了。其次,不要再考虑施法了。

这似乎不会留下太多,但它留下足够的。

假设我们有3个不相关的类型:Alice,Bob和Dense。

任何东西都可以轻松转换为密集:

template<typename T>
Dense to_dense( T&& );

现在,有一些自定义代码可让您将Dense转换为Alice或Bob。让我们使用重载,我们传递一个tag参数告诉我们要转换为:

template<class T> struct type_tag {};
Alice from_dense( Dense const&, type_tag<Alice> );
Bob from_dense( Dense const&, type_tag<Bob> );

这些将由AliceBob尊重地维护。

我们假设AliceBob属于某种类型 - 称之为ConvertUniverse

template<class T>
constexpr bool InConvertUniverse();

我们可以在编译时说InConvertUniverse<Alice>()并获取trueInConvertUniverse<int>()并获取false。目前,Dense不在所说的宇宙中。

让我们创建一个可以自动将所述Universe中的任何内容转换为其他内容的函数:

template<class T> using decay_t = typename std::decay<T>::type;

template<class Src, class Dest>
typename std::enable_if<
  InConvertUniverse< decay_t<Src> >() && InConvertUniverse< Dest >(),
  Dest
>::type
cross_convert( Src&& src, type_tag<Dest> ) {
  return from_dense( to_dense(src), type_tag<Dest>{} );
}

现在,cross_convert( alice, type_tag<Bob>{} )将返回Bob

我们可以使用此功能对template内的任何InConvertUniverse<?>()实施Alice强制转换。实际上,我们可以使用它在Alice的CRTP父级中实现此类强制转换。

对于来自Dense的投射,你们都很好。

下一步是在特殊情况下允许更快cross_convert。如果你做到这一点,你将需要使上面的全局cross_convert具有一些属性,使其不像ADL找到的重载那样优先(例如用...或{{结束参数列表1}}你从未使用过)。然后我会相信特定的Ts&&...超载。

现在,令人烦恼的上述错误是ADL不允许我们将cross_convert粘贴到from_dense的命名空间中并自动找到它,因为Bob存在于type_tag<Dest>{}中不相关的命名空间(理论上)。因此,请将type_tag替换为:

template<class T> using type_tag = T*;

并且ADL开始启动,我们可以在from_dense的命名空间中定义Alice,并通过cross_convert神奇地找到它。它还会导致协变过载,如果你有Alice的子类,这很烦人,但这不是问题。