Variadic 模板双端包解决方法

Variadic templates double-ended pack workaround

以下代码可以编译,因为我假设 Object<Parent, T, Rest...>parent 本身没有父代。

template <typename Parent, typename T, typename... Rest>
struct Object {
    T item;  // T is item's type, while Parent is parent's item's type.
    Object<T, Rest...>* child;  // child's item type is the first type from Rest...
    Object<void, Parent, T, Rest...>* parent;
    Object(const T& t) : item(t) {}
    void setChild (Object<T, Rest...>* c) {
        child = c;
        child->setParent(this);
    }
    void setParent (Object<void, Parent, T, Rest...>* p) {parent = p;}
};

template <typename Parent, typename T>
struct Object<Parent, T> {  // Has no child.
    T item;
    Object<void, Parent, T>* parent;
    Object(const T& t) : item(t) {}
    void setParent (Object<void, Parent, T>* p) {parent = p;}
};

template <typename... Args>
using ObjectWithNoParent = Object<void, Args...>;

int main() {
    ObjectWithNoParent<int, char, double> object(2);
    Object<int, char, double> child('r');
    object.setChild(&child);
    Object<char, double> grandChild(3.5);
//    child.setChild(&grandChild);  // Want this line to work.
}

我可以使用什么解决方法来将类型 void 替换为通用类型?

编译器必须能够分辨出一个参数包从哪里开始,另一个参数包从哪里结束。为此,您所要求的不是 直接 可能的,但是有一个解决方法:使用另一个可变参数模板作为 "pack delimiter."

我们的目标是支持这样的类型:

Object<pack<A, B>, C, pack<D, E>> foo;

在这种情况下,Cfoo 的值类型,它将具有以下相关类型:

Object<pack<A>, B, pack<C, D, E>> foo_parent;
Object<pack<A, B, C>, D, pack<E>> foo_child;

Parent-less 类型的第一包为空,child-less 类型的第二包为空。

由于您不能 "recurse backwards" 通过可变参数,所以这很复杂,因此具有 parent 的类型会有点混淆。 (不幸的是,方法 template <typename... Types, typename Last> struct foo 不起作用,因为参数包必须是模板参数列表中的 last 东西。)我们需要更多的助手来获取并且"peel off" 可变参数包中的最后一个类型(将 pack<A, B, C> 更改为 pack<A, B>)。

我们将从 pack:

的声明开始
// Helper template; needs no definition since we never instantiate it.
template <typename...> struct pack;

现在我们需要一个助手来获取包中的最后一个类型;给定 pack<A, B, C> 它应该让我们获得 C。为此,我们定义了一个 meta-function,它将从包的开头删除类型,直到只剩下一个。

// Helper to allow us to obtain the last type from a pack.
template <typename> struct last_type_in_pack;

template <typename T>
struct last_type_in_pack<pack<T>>
{
    typedef T type;
};

template <typename First, typename... Types>
struct last_type_in_pack<pack<First, Types...>>
    : public last_type_in_pack<pack<Types...>> { };

现在我们需要帮手把pack<A, B, C>变成pack<A, B>。我们将在为 object 构建 parent 类型时使用它。此 meta-function 的工作方式是从模板参数 pack<>, pack<A, B, C> 开始并将右侧包的 left-most 类型移动到左侧包中的 right-most 位置。当 right-most 包还剩下一种类型时,left-most 包就是我们的最终类型。

如果这很难理解,请执行以下步骤:

  1. pack<>, pack<A, B, C> 开始。正确的包不止一个元素,所以我们继续。
  2. 现在我们有 pack<A>, pack<B, C>。右边的包还是不止一个元素
  3. 现在我们有 pack<A, B>, pack<C>。右边的包有一个元素,所以左边的包是我们的最终类型。

实施:

// We need another helper to allow us to "peel off" the last type from a pack,
// turning pack<A, B, C> into pack<A, B> for example.
template <typename, typename> struct remove_last_type_from_pack_impl;

template <typename... Types, typename LastType>
struct remove_last_type_from_pack_impl<pack<Types...>, pack<LastType>>
{
    typedef pack<Types...> type;
};

template <typename... TS1, typename T2, typename... TS2>
struct remove_last_type_from_pack_impl<pack<TS1...>, pack<T2, TS2...>>
    : public remove_last_type_from_pack_impl<pack<TS1..., T2>, pack<TS2...>> { };

template <typename>
struct remove_last_type_from_pack;

template <typename... Types>
struct remove_last_type_from_pack<pack<Types...>>
    : public remove_last_type_from_pack_impl<pack<>, pack<Types...>> { };

现在我们实际声明 Object。我们没有定义它,因为我们将为每个预期的实例化提供部分特化。如果有人试图将此模板与我们不支持的参数一起使用,他们只会收到 "incomplete type" 错误,这正是我们想要的。

template <typename...> struct Object;

部分专业化允许 "double-ended" 包,并且需要至少一种 parent 类型和一种 child 类型。 (FirstParent 的存在只是为了强制当第一个包为空时,此部分特化将不匹配。)

template <typename FirstParent, typename... ParentTypes,
          typename T,
          typename FirstChild, typename... ChildTypes>
struct Object<pack<FirstParent, ParentTypes...>, T, pack<FirstChild, ChildTypes...>>
{
    // We'll shift the packs around T to define our child and parent types:
    typedef Object<pack<FirstParent, ParentTypes..., T>,
                   FirstChild,
                   pack<ChildTypes...>> child_type;

    typedef Object<
        typename remove_last_type_from_pack<pack<FirstParent, ParentTypes...>>::type,
        typename last_type_in_pack<pack<FirstParent, ParentTypes...>>::type,
        pack<T, FirstChild, ChildTypes...>> parent_type;

    T item;
    child_type * child;
    parent_type * parent;

    Object(T const & t) : item(t) { }
    Object(T && t) : item(std::move(t)) { }
};

现在我们需要 parent-less 和 child-less 的特化。

parent-less类型比较简单:

template <typename T, typename FirstChild, typename... ChildTypes>
struct Object<pack<>, T, pack<FirstChild, ChildTypes...>>
{
    typedef Object<pack<T>, FirstChild, pack<ChildTypes...>> child_type;

    T item;
    child_type * child;

    Object(T const & t) : item(t) { }
    Object(T && t) : item(std::move(t)) { }
};

现在是 child-less 类型。和以前一样,FirstParent 只是确保我们至少有一个 parent 类型。

我们必须做同样的体操才能获得parent类型。

template <typename FirstParent, typename... ParentTypes, typename T>
struct Object<pack<FirstParent, ParentTypes...>, T, pack<>>
{
    typedef Object<
        typename remove_last_type_from_pack<pack<FirstParent, ParentTypes...>>::type,
        typename last_type_in_pack<pack<FirstParent, ParentTypes...>>::type,
        pack<T>> parent_type;

    T item;
    parent_type * parent;

    Object(T const & t) : item(t) { }
    Object(T && t) : item(std::move(t)) { }
};

请注意,此时 Object<pack<>, T, pack<>> 无法实例化,因为没有与之匹配的专业化,并且未定义基本模板。这种类型并没有多大意义,IMO,但如果你愿意,你可以为它专门化 Object:

template <typename T>
struct Object<pack<>, T, pack<>>
{
    T item;

    Object(T const & t) : item(t) { }
    Object(T && t) : item(std::move(t)) { }
};

如果您仍然想要 ObjectWithNoParent 模板别名,现在是这样的:

template <typename Arg, typename... Rest>
using ObjectWithNoParent = Object<pack<>, Arg, pack<Rest...>>;

Here is a sample 并没有做太多,但它确实表明它可以编译并且它断言 parent 和 child 类型是我们所期望的。)