简体   繁体   中英

C++ method with multiple parameter packs

Consider the following simplified piece of code for a variant class. Most of it is for informational purposes, the question is about the conditional_invoke method.

// Possible types in variant.
enum class  variant_type { empty, int32, string };

// Actual data store.
union variant_data { 
    std::int32_t val_int32;
    std::string val_string;
    inline variant_data(void) { /* Leave uninitialised */ }
    inline ~variant_data(void) { /* Let variant do clean up. */ }
};

// Type traits which allow inferring which type to use (these are actually generated by a macro).
template<variant_type T> struct variant_type_traits { };
template<class T> struct variant_reverse_traits { };

template<> struct variant_type_traits<variant_type::int32> {
    typedef std::int32_t type;
    inline static type *get(variant_data& d) { return &d.val_int32; }
};

template<> struct variant_reverse_traits<std::int32_t> {
    static const variant_type type = variant_type::int32;
    inline static std::int32_t *get(variant_data& d) { return &d.val_int32; }
};

template<> struct variant_type_traits<variant_type::string> {
    typedef std::string type;
    inline static type *get(variant_data& d) { return &d.val_string; }
};

template<> struct variant_reverse_traits<std::string> {
    static const variant_type type = variant_type::string;
    inline static std::string *get(variant_data& d) { return &d.val_string; }
};

// The actual variant class.
class variant {
public:

    inline variant(void) : type(variant_type::empty) { }

    inline ~variant(void) {
        this->conditional_invoke<destruct>();
    }

    template<class T> inline variant(const T value) : type(variant_type::empty) {
        this->set<T>(value);
    }

    template<class T> void set(const T& value) {
        this->conditional_invoke<destruct>();
        std::cout << "Calling data constructor ..." << std::endl;
        ::new (variant_reverse_traits<T>::get(this->data)) T(value);
        this->type = variant_reverse_traits<T>::type;
    }

    variant_data data;
    variant_type type;

    private:

    template<variant_type T> struct destruct {
        typedef typename variant_type_traits<T>::type type;
        static void invoke(type& v) {
            std::cout << "Calling data destructor ..." << std::endl;
            v.~type(); 
        }
    };

    template<template<variant_type> class F, class... P>
    inline void conditional_invoke(P&&... params) {
        this->conditional_invoke0<F, variant_type::int32, variant_type::string, P...>(std::forward<P>(params)...);
    }

    template<template<variant_type> class F, variant_type T, variant_type... U, class... P>
    void conditional_invoke0(P&&... params) {
        if (this->type == T) {
            F<T>::invoke(*variant_type_traits<T>::get(this->data), std::forward<P>(params)...);
        }
        this->conditional_invoke0<F, U..., P...>(std::forward<P>(params)...);
    }

    template<template<variant_type> class F, class... P>
    inline void conditional_invoke0(P&&... params) { }
};

The code works this way, ie it works as long as the parameter list P... for the functor is empty. If I add another functor like

template<variant_type T> struct print {
    typedef typename variant_type_traits<T>::type type;
    static void invoke(type& v, std::ostream& stream) {
        stream << v;
    }
};

and try to invoke it

friend inline std::ostream& operator <<(std::ostream& lhs, variant& rhs) {
    rhs.conditional_invoke<print>(lhs);
    return lhs;
}

the compiler VS 20115 complains

error C2672: 'variant::conditional_invoke0': no matching overloaded function found

or gcc respectively

error: no matching function for call to 'variant::conditional_invoke0 >&>(std::basic_ostream&)'

I guess the compiler cannot decide when U... ends and when P... starts. Is there any way to work around the issue?

You'll have to make both parameter packs deducible. That is, let the type and non-type template parameters be part of a function parameter list. For that, introduce a dummy structure:

template <variant_type...>
struct variant_type_list {};

and let the compiler deduce the variant_type... pack from a function call:

template <template <variant_type> class F
        , variant_type T
        , variant_type... U
        , typename... P>
void conditional_invoke0(variant_type_list<T, U...> t
                       , P&&... params)
{
    if (this->type == T)
    {
        F<T>::invoke(*variant_type_traits<T>::get(this->data)
                    , std::forward<P>(params)...);
    }

    this->conditional_invoke0<F>(variant_type_list<U...>{}
                               , std::forward<P>(params)...);
}

To break recursive calls, introduce an overload with an empty variant_type_list :

template <template <variant_type> class F, typename... P>
void conditional_invoke0(variant_type_list<>, P&&... params) {}

When calling the invoker for the first time, provide variant_types as an argument:

this->conditional_invoke0<F>(variant_type_list<variant_type::int32, variant_type::string>{}
                           , std::forward<P>(params)...);

DEMO

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM