Возможно ли распутать шаблон из его аргументов в С++?

Предположим, что я получаю два аргумента шаблону 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()));
}

Я что-то упустил? Есть ли лучший подход?

ПОЧЕМУ Я хочу это сделать?

Я создаю С++-библиотеку, где я хочу упростить то, что пользователям нужно делать для создания модульных шаблонов. Например, если пользователь хочет создать симуляцию на основе агентов, он может настроить шаблон World с типом организма, менеджером по популяции, менеджером среды и менеджером системы.

Каждый из менеджеров также должен знать тип организма, поэтому декларация может выглядеть примерно так:

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

Я бы предпочел, чтобы пользователям не приходилось повторять NeuralNetworkAgent каждый раз. Если я могу изменить аргументы шаблона, можно использовать аргументы по умолчанию, и приведенное выше можно упростить, чтобы:

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

Плюс легче преобразовать из одного типа мира в другой, не беспокоясь о ошибках типа.

Конечно, я могу справиться с большинством ошибок с помощью static_assert и просто иметь дело с более длинными объявлениями, но я хотел бы знать, возможно ли лучшее решение.

Ответ 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;
}

Лучший вопрос на С++ на этой неделе.

Ответ 2

Это в основном две отдельные проблемы: как разложить экземпляр шаблона класса в шаблон класса, а затем как взять шаблон шаблона и создать его экземпляр. Пойдем с принципом, что метапрограммирование шаблона проще, если все всегда является типом.

Во-первых, вторая часть. Учитывая шаблон класса, включите его в класс metafunction:

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 (по крайней мере, это кажется мне подходящим). Это метафункция, которая берет тип и дает класс metafunction:

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;

Ответ 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 - это более удобный способ сделать это, что полезно для более сложного метапрограммирования, но это чрезмерный избыток ИМО для использования. Это просто.

Переадресация произвольных специализированных шаблонов на другой набор аргументов шаблона невозможна в С++; в большинстве случаев вы можете иметь дело с подмножеством (в первую очередь шаблоны, в которых используются только параметры типа, а также некоторые другие комбинации, которые вы можете поддержать), и даже тогда есть многочисленные проблемы. Правильное переупорядочение std::unordered_set<int, my_fancy_hash<int>, std::equal_to<>, std::pmr::polymorphic_allocator<int>> требует знаний, характерных для используемых шаблонов.