Шаблоны функций Variadi c с двумя (или более) определенными c пакетами (специализация / перегрузка) - PullRequest
5 голосов
/ 06 апреля 2020

Функция 'Процесс' принимает переменное число аргументов типа переменной . Для обработки разных случаев я успешно перегрузил его следующим образом:

// general case
template <typename ...Types>
void Process( const Types&... items )

// single T
template <typename T>
void Process( const T& t )

// one or more of type NVP<>
template <typename T1, typename ...Types>
void Process( const NVP<T1>& nvp1, const NVP<Types>&... nvps )

Что я хочу сделать - но не могу - это следующее : мне нужна перегрузка для случаев с любое количество ведущих аргументов типов ATT<>, за которыми следует любое число NVP<>, например:

// any number of leading Types ATT<> followed by any number of NVP<>
template <typename ...ATypes, typename ...BTypes>
void Process( const ATT<ATypes>&... atts, const NVP<BTypes>&... nvps )

Сначала вы можете подумать, что компилятору должно быть «легко» соответствовать этому, если это может уже делать другие случаи. Здесь не должно быть абсолютно никакой двусмысленности !? Однако сопоставление завершается неудачно, сообщений об ошибках нет, но требуемая перегрузка просто игнорируется компилятором.

В настоящее время используется VS2017 с / std: c ++ 17


Примечания:

1. Очевидно, что это можно сделать для один начальный тип ATT<T1> вот так

// one leading Type ATT<T1>
template <typename T1, typename ...Types>
void Process( const ATT<T1>& a1, const Types&... remaining )

Но для более чем одного Мне нужно сделать некрасивую ручную рекурсию. Я действительно хочу, чтобы весь пакет ведущих составлял ATT<...>.

2. Я знаю, что ведущий пакет параметров - общих типов - всегда неоднозначен для сопоставления, но для специализации типа ATT<ATypes>... двусмысленности быть не должно.

Ответы [ 3 ]

2 голосов
/ 13 апреля 2020

Вы можете выполнить диспетчеризацию от перегрузки const Types&... в зависимости от того, соответствует ли Types... ATT<T>..., NVP<U>....

Основная стратегия c - найти индекс последней ATT<T>, переадресовав все как кортеж, затем индексирование с соответствующей последовательностью индекса для пересылки в другую функцию, где значения ATT и NVP находятся в двух кортежах:

namespace detail {
    template<class...>
    struct get_split_index;

    template<class T, class... Others>
    struct get_split_index<T, Others...> {
        static constexpr std::size_t i = -1;
    };

    template<class T, class... Others>
    struct get_split_index<ATT<T>, Others...> {
        static constexpr std::size_t next = get_split_index<Others...>::i;
        static constexpr std::size_t i = next == -1 ? -1 : next + 1u;
    };

    template<class T, class... Others>
    struct get_split_index<NVP<T>, Others...> {
        // will be 0 if the rest are all NVP<T>, otherwise -1
        static constexpr std::size_t i = get_split_index<Others...>::i;
    };

    template<>
    struct get_split_index<> {
        static constexpr std::size_t i = 0;
    };

    template<typename... ATypes, typename... BTypes, std::size_t... ATT_I, std::size_t... NVP_I>
    void Process(const std::tuple<const ATT<ATypes>&...>& att, const std::tuple<const NVP<BTypes>&...>& nvp, std::index_sequence<ATT_I...>, std::index_sequence<NVP_I...>) {
        // Use (std::get<ATT_I>(att)) and (std::get<NVP_I>(nvp))
        // instead of (atts) and (nvps) that you would use in your
        // supposed `void Process(const ATT<ATypes>&..., const NVP<BTypes>&...)`
    }

    template<typename... Types, std::size_t... ATT_I, std::size_t... NVP_I>
    void ProcessDispatch(const std::tuple<Types...>& t, std::index_sequence<ATT_I...> att_i, std::index_sequence<NVP_I...> nvp_i) {
        detail::Process(std::forward_as_tuple(std::get<ATT_I>(t)...), std::forward_as_tuple(std::get<NVP_I + sizeof...(ATT_I)>(t)...), att_i, nvp_i);
    }
}

template <typename ...Types>
void Process( const Types&... items ) {
    constexpr std::size_t split_index = detail::get_split_index<Types...>::i;
    if constexpr (split_index != -1) {
        // Might want to check `&& sizeof...(Types) != 0`
        detail::ProcessDispatch(std::forward_as_tuple(items...), std::make_index_sequence<split_index>{}, std::make_index_sequence<sizeof...(Types) - split_index>{});
    } else {
        // general case
    }
}

template <typename T>
void Process( const T& t ) {
    // single T
}


template <typename T1, typename ...Types>
void Process( const NVP<T1>& nvp1, const NVP<Types>&... nvps ) {
    // one or more of type NVP<>
    // This can also be folded into `detail::Process`, checking
    // `if constexpr (sizeof...(BTypes) == 0)`.
}

0 голосов
/ 10 апреля 2020

Предполагая, что вы в порядке получения их в виде кортежей, я сделал это после рисования из { ссылка }:

#include <iostream>
#include <tuple>

template<typename T>
struct ATT {};

template<typename T>
struct NVP {};

template<typename... ATTs, typename... NVPs>
void Process(const std::tuple<ATT<ATTs>...>& atts, const std::tuple<NVP<NVPs>...>& nvps) {
    std::cout << sizeof...(ATTs) << std::endl;
    std::cout << sizeof...(NVPs) << std::endl;
}

int main() {
    Process(std::make_tuple(ATT<int>(), ATT<double>()), std::make_tuple(NVP<std::string>(), NVP<bool>()));

    return 0;
}

Он компилируется в https://www.onlinegdb.com/online_c++_compiler , но я не могу тестировать в визуальной студии.

0 голосов
/ 10 апреля 2020

Поверьте, вы можете использовать структуру, чтобы помочь вам здесь. Компилятор не может определить, где останавливается один пакет параметров, а начинается другой, рассмотрим:

foo(1, 2.0, '3', "45", 6.0f). Первый пакет параметров может быть ничем, первым, всеми из них или ничего из вышеперечисленного. Нет особой причины предпочитать одно другому. Таким образом, вы не можете создать функцию, которая принимает две вариации. Что вы можете сделать, это разделить его на две структуры и указать явно аргументы для внешнего класса.

template<typename... Args>
struct S
{
    template<typename... Inner>
    static void Process(const ATT<Args>&... atts, const NVP<Inner>&... nvps) {}
};

Пример использования:

ATT<double> a1;
ATT<long> a2;
NVP<int> n1;
NVP<const char*> n2;

S<double, long>::Process(a1, a2, n1, n2);

Другая версия может быть с помощью конструктора. Здесь вы также получаете автоматическое удержание, что проще. К сожалению, это работает только с C ++ 17 и выше.

template<typename... Args>
struct S
{
    std::tuple<ATT<Args>...> tup;

    S(const ATT<Args>&... atts)
        : tup(atts...)
    {}

    template<typename... Inner>
    void Process(const NVP<Inner>&... nvps){}
};

template<typename... Args>
S(const ATT<Args>&... atts)->S<Args...>;

И использование:

S(ATT(1), ATT(3.4)).Process(NVP("asdf"), NVP(3.4), NVP('f'));
return 0;
...