【问题标题】:Is there a clean(er) way to mix CRTP with variadic inheritance?有没有一种干净的方法来混合 CRTP 和可变参数继承?
【发布时间】:2026-02-19 20:50:02
【问题描述】:

最初我无法找到解决this 的方法,但当我提出这个问题时,我想到了新的搜索词,我终于找到了答案。我认为这篇文章既可以作为任何有相同问题的人的重定向(因为需要一段时间才能找到),我还想看看是否有任何方法可以改进语法糖,因为答案是 9 年旧的和一些现代的功能当时不可用。该答案的代码:

#include <utility>

template <template<class> class... Mixins>
class Host : public Mixins<Host<Mixins...>>...
{
  public:
    Host(Mixins<Host>&&... args) : Mixins<Host>(std::forward<Mixins<Host>>(args))... {}
};

template <class Host> struct Mix1 {};
template <class Host> struct Mix2 {};

int main (void)
{
  typedef Host<Mix1, Mix2> TopHost;
  delete new TopHost(Mix1<TopHost>(), Mix2<TopHost>());
}

我们的目标是消除在每个 mixin 中重复使用 TopHost 的需要,因为它有点烦人,而且(更重要的是)如果错误的类型意外地与 CRTP 一起使用,它可能真的搞砸了。我有一个偷偷摸摸的怀疑它可能是可能的,因为使用可变参数调用模板类型的构造函数当然是可能的(例如 new T(args...))。

理想情况下,我们可以使用如下语法:

auto mix = TopHost(Mix1(args1...), Mix2(args2...), ...);

甚至(以某种方式)从 using 语句中推导出 mixins:

auto mix = TopHost({args1...}, {args2...}, ...);

编辑: 所以,是的,确实,我在不知不觉中为我自己的问题提供了解决方案。第二种语法有效。如前所述,它不需要使用,因此仍然存在用户错误的可能性。 @Evg 的解决方案确实强制了这一点,虽然它更冗长,但它在技术上回答了这个问题,所以我会接受这个答案(现在)。

现在我遇到的问题是,在我的应用程序中,mixin 已经删除了复制构造函数,并且这两种方法都创建了副本(当然,原来的类就是这样设计的)。所以现在问题变成了:有没有办法在不复制的情况下实现语法?我一直在尝试让这样的东西工作,但似乎无法围绕如何扩展不同大小的可变参数模板来解决问题:

template < typename... > struct TypeList {};
template<typename TypeList> struct Variad;
template<typename... Types> struct Variad<TypeList<Types...>> {};

template<template<class> typename ...Args> struct Mixins;
template< class TemplList, class... Lists> struct Host;

template< template<class> class ...Components,  template<class...> class T, class... Variads>
struct Host<Mixins<Components...>, T<Variads>...> : 
  public Components<Host<Mixins<Components...>, T<Variads>...>>...
{
    Host(T<Variads>&&... args) : Components<Host<Mixins<Components...>, T<Variads>...>>(std::forward<T<Variads>...>(args))... {}
};

这样使用:

int main() {
  using f1 = TypeList<int,int>;
  using f2 = TypeList<int>;
  using m1 = Mixins<Mix1, Mix2>;
  Host<m1, Variad<f1>, Variad<f2>> obj(1,2,3);
}

不一定干净,但在我的情况下更可取。我不太确定其中的扩展究竟是如何工作的,但我基于我尝试创建似乎正常工作的嵌套变量:


#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#define get_type(x) (abi::__cxa_demangle(typeid(x).name(), NULL, NULL, NULL))

template < typename... > struct TypeList {};
template<typename TypeList> struct Variad;
template<typename... Types> struct Variad<TypeList<Types...>> {};

template<typename ...TypeListOne> struct NestedVariad;
template<template<class...> class T, typename...Types>
struct NestedVariad<T<Types>...> { };


int main() {
  using f1 = TypeList<int,int>;
  using f2 = TypeList<int>;

  NestedVariad<Variad<f1>, Variad<f2>> obj;
  cout << get_type(obj) << endl;
}

哪个输出:NestedVariad&lt;Variad&lt;TypeList&lt;int, int&gt; &gt;, Variad&lt;TypeList&lt;int&gt; &gt; &gt;

但是对 mixin 类使用类似的方法将所有三个参数传递给每个构造函数,而不是 2 传递给第一个,1 传递给第二个

【问题讨论】:

  • 第二种语法有效:demo.
  • @Oliv Heeeey,确实如此。我无意中(有点)回答了我自己的问题。但是,该解决方案 A:不强制使用语法,因此用户仍有可能以原始方式执行此操作,并且 B:要求所有 mixin 实现使用初始化列表的构造函数

标签: c++ variadic-templates multiple-inheritance crtp syntactic-sugar


【解决方案1】:

正如 Oliv 在评论中所说,第二种语法有效:

template <class Host> struct Mix1 {
    Mix1(int, int) {}
};

template <class Host> struct Mix2 {
    Mix2(int) {}
};

using TopHost = Host<Mix1, Mix2>;
auto mix = TopHost({1, 1}, {2});

或者,您可以这样做:

template<class TopHost, class Mixes, class Tuple, std::size_t... ii>
auto make_mix_impl(std::index_sequence<ii...>, Tuple&& tuple)
{
    return TopHost(std::make_from_tuple<std::tuple_element_t<ii, Mixes>>(
        std::get<ii>(std::forward<Tuple>(tuple)))...);
}

template<template<class> class... Mixes, class... Tuples>
auto make_mix(Tuples&&... tuples)
{
    static_assert(sizeof...(Mixes) == sizeof...(Tuples));
    using TopHost = Host<Mixes...>;
    return make_mix_impl<TopHost, std::tuple<Mixes<TopHost>...>>(
        std::make_index_sequence<sizeof...(Mixes)>{}, 
        std::make_tuple(std::forward<Tuples>(tuples)...));
}

auto mix = make_mix<Mix1, Mix2>(std::make_tuple(1, 1), std::make_tuple(2));

【讨论】:

    最近更新 更多