首页 > 解决方案 > 具有两个(或更多)特定包(专业化/重载)的可变参数函数模板

问题描述

函数 'Process' 采用可变数量的变量类型参数。为了处理不同的情况,我成功地重载了它,如下所示:

// 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 )

起初你会认为编译器匹配这个应该是“容易的”,如果它已经可以做其他情况的话。这里绝对没有歧义!?但是,匹配失败,没有错误消息,但是编译器忽略了所需的重载。

当前使用带有 /std:c++17 的 VS2017


笔记:

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>...没有模棱两可的专业化来说不应该存在。

标签: c++c++17variadic-templatesvariadic-functionsoverload-resolution

解决方案


const Types&...您可以根据 ifTypes...匹配从重载中分派ATT<T>..., NVP<U>...

这里的基本策略是找到 last 的索引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)`.
}


推荐阅读