【问题标题】:Is it possible to disentangle a template from its arguments in C++? [duplicate]是否可以在 C++ 中将模板与其参数分开? [复制]
【发布时间】:2016-04-09 02:16:49
【问题描述】:

假设我收到一个模板的两个参数,T1 和 T2。如果我知道 T1 本身是一个模板类(例如,一个容器),而 T2 可以是任何东西,我是否可以确定 T1 的基本模板类型并使用 T2 作为其参数重新构建它?

例如,如果我收到std::vector<int>std::string,我想自动构建std::vector<std::string>。但是,如果给我std::set<bool>double,它将产生std::set<double>

在这里查看 type_traits、相关博客和其他问题后,我没有看到解决此问题的通用方法。我目前认为完成此任务的唯一方法是为可以作为 T1 传入的每种类型构建模板适配器。

例如,如果我有:

template<typename T_inner, typename T_new>
std::list<T_new> AdaptTemplate(std::list<T_inner>, T_new);

template<typename T_inner, typename T_new>
std::set<T_new> AdaptTemplate(std::set<T_inner>, T_new);

template<typename T_inner, typename T_new>
std::vector<T_new> AdaptTemplate(std::vector<T_inner>, T_new);

我应该能够使用 decltype 并依靠运算符重载来解决我的问题。大致如下:

template <typename T1, typename T2>
void MyTemplatedFunction() {
  using my_type = decltype(AdaptTemplate(T1(),T2()));
}

我错过了什么吗?有更好的方法吗?

我为什么要这样做?

我正在构建一个 C++ 库,我想在其中简化用户构建模块化模板所需的操作。例如,如果用户想要构建基于代理的模拟,他们可能会配置具有生物类型、种群管理器、环境管理器和系统管理器的 World 模板。

每个管理者还需要知道有机体类型,因此声明可能类似于:

World< NeuralNetworkAgent, EAPop<NeuralNetworkAgent>,
       MazeEnvironment<NeuralNetworkAgent>,
       LineageTracker<NeuralNetworkAgent> > world;

我更希望用户不必每次都重复NeuralNetworkAgent。如果我能够更改模板参数,则可以使用默认参数,以上可以简化为:

World< NeuralNetworkAgent, EAPop<>, MazeEnvironment<>, LineageTracker<> > world;

此外,从一种世界类型转换为另一种世界类型更容易,而无需担心类型错误。

当然,我可以使用 static_assert 处理大多数错误,只处理较长的声明,但我想知道是否有更好的解决方案。

【问题讨论】:

  • 我得到的三个答案都很好。 @Barry 的一篇是最彻底的,T.C. 的一篇是最彻底的。最好地解决了我的潜在问题,但我认为 Sam Varshavchik(我接受了)的回答对于解决我提出的问题是最优雅的。非常感谢大家!
  • 至于这篇文章是重复的,我确实认为@Ben Voight 指出的那个非常相似,但特别关注 STL。如果所有有问题的模板都来自同一个库,那么肯定还有其他可能的技巧。也就是说,另一个问题也有一些有趣且有用的答案。也就是说,我发现这里提供的答案更针对这个问题并且更直接有用。
  • 虽然另一个问题不那么广泛,但答案完全涵盖了您的情况。这就是为什么我的标志会导致横幅显示“您的问题已经在这里有了答案”。

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


【解决方案1】:

这似乎以您所询问的方式工作,用 gcc 5.3.1 测试:

#include <vector>
#include <string>

template<typename T, typename ...U> class AdaptTemplateHelper;

template<template <typename...> class T, typename ...V, typename ...U>
class AdaptTemplateHelper<T<V...>, U...> {
 public:

    typedef T<U...> type;
};

template<typename T, typename ...U>
using AdaptTemplate=typename AdaptTemplateHelper<T, U...>::type;

void foo(const std::vector<std::string> &s)
{
}

int main()
{
    AdaptTemplate<std::vector<int>, std::string> bar;

    bar.push_back("AdaptTemplate");
    foo(bar);
    return 0;
}

本周最佳 C++ 问题。

【讨论】:

  • 每当我认为自己能够轻松处理微妙的元编程问题并了解 C++ 的界限时,我都会在这里提出一个问题并学到一些全新的东西。谢谢!
【解决方案2】:

这基本上是两个独立的问题:如何将类模板的实例化分解为类模板,然后如何获取类模板并对其进行实例化。让我们遵循模板元编程更容易的原则,如果一切都是类型。

首先,第二部分。给定一个类模板,让我们把它变成一个元函数类:

template <template <typename...> class F>
struct quote {
    template <typename... Args>
    using apply = F<Args...>;
};

这里,quote&lt;std::vector&gt; 是一个元函数类。它是具有成员模板apply 的具体类型。所以quote&lt;std::vector&gt;::apply&lt;int&gt; 给你std::vector&lt;int&gt;

现在,我们需要解压一个类型。让我们称之为unquote(至少这对我来说似乎合适)。这是一个接受类型并产生一个元函数类的元函数:

template <class >
struct unquote;

template <class T>
using unquote_t = typename unquote<T>::type;

template <template <typename...> class F, typename... Args>
struct unquote<F<Args...>> {
    using type = quote<F>;
};

现在您需要做的就是将实例化传递给unquote,并将您想要的新参数提供给它吐出的元函数类:

unquote_t<std::vector<int>>::apply<std::string>

对于您的具体情况,只需quote 一切:

// I don't know what these things actually are, sorry
template <class Agent, class MF1, class MF2, class MF3>
struct World {
    using t1 = MF1::template apply<Agent>;
    using t2 = MF2::template apply<Agent>;
    using t3 = MF3::template apply<Agent>;
};


World< NeuralNetworkAgent,
    quote<EAPop>,
    quote<MazeEnvironment>,
    quote<LineageTracker>
> w;

【讨论】:

  • 这种方法的问题是不能使用非默认分配器、比较器等
  • @T.C.无论如何,比较器的交换都是有问题的,如果比较器不是模板怎么办?最好明确地提供它。当然,使用分配器,我可以写一个不同类型的quote,这没什么大不了的。
  • @T.C.对其他解决方案没有相同的评论?
【解决方案3】:

你的实际问题可以通过模板模板参数来解决。

template <class Agent, template<class...> class F1,
                       template<class...> class F2,
                       template<class...> class F3>
struct World {
    // use F1<Agent> etc.
};

World<NeuralNetworkAgent, EAPop, MazeEnvironment, LineageTracker > world;

@Barry 的 quote 是一种更出色的方法,这对于更复杂的元编程很有用,但对于这么简单的用例来说,IMO 有点矫枉过正。

在 C++ 中不可能将任意模板特化重新绑定到一组不同的模板参数;最多你可以处理一个子集(主要是模板只接受类型参数,加上你可能选择支持的一些其他组合),即使这样也有很多问题。正确重新绑定std::unordered_set&lt;int, my_fancy_hash&lt;int&gt;, std::equal_to&lt;&gt;, std::pmr::polymorphic_allocator&lt;int&gt;&gt; 需要特定于所使用模板的知识。

【讨论】:

    猜你喜欢
    • 2017-05-25
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 2012-01-11
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    相关资源
    最近更新 更多