是否可以在 C++ 中将模板与其参数分开?

Is it possible to disentangle a template from its arguments in C++?

假设我收到模板的两个参数,T1 和 T2。如果我知道 T1 本身是一个模板化的 class(例如,一个容器),而 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< NeuralNetworkAgent, EAPop<NeuralNetworkAgent>,
       MazeEnvironment<NeuralNetworkAgent>,
       LineageTracker<NeuralNetworkAgent> > world;

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

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

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

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

这似乎以您询问的方式工作,已使用 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++ 问题。

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

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

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

这里,quote<std::vector>是元函数class。它是具有成员模板 apply 的具体类型。所以 quote<std::vector>::apply<int> 给你 std::vector<int>

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

的元函数
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 并将您想要的新参数提供给它吐出的元函数 class:

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;

你的实际问题只要取模板模板参数就可以解决

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<int, my_fancy_hash<int>, std::equal_to<>, std::pmr::polymorphic_allocator<int>> 需要特定于所用模板的知识。