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

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

本文关键字:参数 区分开 C++ 是否      更新时间:2023-10-16

假设我收到一个模板的两个参数,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处理大多数错误,只处理较长的声明,但我想知道是否有更好的解决方案。

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

这基本上是两个独立的问题:如何将类模板的实例化分解为类模板,然后如何获取类模板并实例化它。让我们遵循这样的原则:如果所有东西都是类型,那么模板元编程更容易。

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

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

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

现在,我们需要解压缩一个类型。让我们称之为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;

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

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>>需要特定于所用模板的知识。