5

The function 'Process' is taking a variable number of arguments of variable type. To handle different cases, I have successfully overloaded it like this:

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

What I want to do - but can't - is the following: I need an overload for cases with any number of leading arguments of a types ATT<> followed by any number of NVP<> like this:

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

At first you would think it should be 'easy' for a compiler to match this, if it can already do the other cases. There should be absolutely no ambiguity here!? However, the matching fails, no error messages, but the desired overload it is just ignored by the compiler.

Currently using VS2017 with /std:c++17


Notes:

1. It can, obviously, be done for one leading type ATT<T1> like this

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

But for more than one, I need to do some ugly manual recursion. I really want to have the whole pack of leading ATT<...>.

2. I am aware that a leading parameter pack - of general types - always is ambiguous for matching, but for a specialization like ATT<ATypes>... no ambiguity should exist.

non-user38741
  • 671
  • 5
  • 19
  • perhaps this can help? https://stackoverflow.com/questions/9831501/how-can-i-have-multiple-parameter-packs-in-a-variadic-template – Yucel_K Apr 06 '20 at 11:13
  • Or, here's another potential approach: https://stackoverflow.com/questions/48739516/multiple-parameter-packs-in-a-single-function – Sam Varshavchik Apr 06 '20 at 11:30
  • Thanks for the suggestions so far. The 50 gummy-points really made it attractive, eh. Yes, another level of indirection (tuple, struct, template, etc.) will cure it - as usual. There should be a simpler way, however. Maybe have to wait for C++33. – non-user38741 Apr 10 '20 at 21:05
  • As to reason why it is not as easy as you think. Imagine a parameter X, which is neither `ATT` nor `NVP`. But either could be constructed from X. Obviously a better compiler would complain only when that happens. But here we are... – CygnusX1 Apr 14 '20 at 12:17
  • I'm neither aware nor sure if implicit construction/conversion is supposed to happen for templated parameters like this or templates in general. – non-user38741 Apr 14 '20 at 13:25

3 Answers3

2

You could dispatch from the const Types&... overload based on if Types... matches ATT<T>..., NVP<U>....

The basic strategy here is finding the index of the last ATT<T>, forwarding everything as a tuple, then indexing with the appropriate index sequence to forward to another function where the ATT values and NVP values are in two tuples:

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)`.
}

Artyer
  • 31,034
  • 3
  • 47
  • 75
0

Believe you can use a struct to help you here. The compiler can't determine where one parameter pack stops and the other begins, consider:

foo(1, 2.0, '3', "45", 6.0f). The first parameter pack could be nothing, the first, all of them or none of the above. There is no particular reason to prefer one over another. So you can't make a function that accepts two variadics. What you can do, is to split it into two structs, and specify explicitly the arguments for the outer class.

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

Example for usage:

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

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

Another version could be by using the constructor. Here, you also get auto-deduction which is easier. Unfortunately, it only works from C++17 and above.

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...>;

And the usage is:

S(ATT(1), ATT(3.4)).Process(NVP("asdf"), NVP(3.4), NVP('f'));
return 0;
Michael
  • 932
  • 6
  • 15
0

Assuming you're OK with getting them as tuples I made this after drawing from https://stackoverflow.com/a/12782697/1480324 :

#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;
}

It compiles on https://www.onlinegdb.com/online_c++_compiler , but I can't test in visual studio.

Wutz
  • 2,246
  • 13
  • 15