简体   繁体   中英

unpack variadic template parameters

I have a template function:

template<typename R, typename... T>
void function(const std::string& id, R (*f)(T...)) {
    switch (sizeof...(T)) {
        case 0: f0compile<R>(reinterpret_cast<void (*)()>(f)); break;
        case 1: f1compile<R, T>(reinterpret_cast<void (*)()>(f)); break;
        case 2: f2compile<R, T1, T2>(reinterpret_cast<void (*)()>(f)); break;
    }
    ...
}

How can I call these functions (f0compile, f1compile, f2compile) ? How can I write the "function" ?

template<typename R>
void f0compile(void (*f)()) {
    new F0<R>(f):
    ...
}
template<typename R, typename T>
void f1compile(void (*f)()) {
    new F1<R,T>(f);
    ...
}
template<typename R, typename T1, typename T2>
void f2compile(void (*f)()) {
    new F2<R,T1,T2>(f);
    ...
}

Thank you for help with these variadic template.

I add the implementation of F0 F1 F2:

template <typename R> struct F0 : F {
    F0(void (*_fn)()) : F(typeid(R))
        , fn(reinterpret_cast<R(*)()>(_fn))
    {}
    const void* f() { res = fn(); return &res; }
    R res; R (*fn)();
    void d() { delete this; }
};

template <typename R, typename T> struct F1 : F {
    F1(void (*_fn)(), F* _opd) : F(typeid(R))
        , fn(reinterpret_cast<R(*)(T)>(_fn))
        , opd(autocast<T>(_opd))
    {}
    const void* f() { res = fn(*(T*) opd->f()); return &res; }
    F* opd;
    R res; R (*fn)(T);
    void d() { opd->d(); delete this; }
};

template <typename R, typename T1, typename T2> struct F2 : F {
    F2(void (*_fn)(), F* _opd1, F* _opd2) : F(typeid(R))
        , fn(reinterpret_cast<R(*)(T1,T2)>(_fn))
        , opd1(autocast<T1>(_opd1))
        , opd2(autocast<T2>(_opd2))
    {}
    const void* f() { res = fn(*(T1*) opd1->f(), *(T2*) opd2->f()); return &res; }
    F* opd1; F* opd2;
    R res; R (*fn)(T1,T2);
    void d() { opd1->d(); opd2->d(); delete this; }
};

Thank you

struct F {
            F(const std::type_info& _type) : type(_type) {}
            virtual ~F() {}
            const std::type_info& type;
            virtual const void* f() = 0;
            virtual void d() = 0;
        };      

Added class F . It rapresent each function / operand on the stack

template <typename T> struct Opd : F {
        Opd(T _opd) : F(typeid(T)), res(_opd) { }
        const void* f() { return &res; }
        T res;
        void d() { delete this; }
    };

Added class Opd . It represent a specific operand on the stack.

The real program is this (simplified):

double foo(double op1, double op2) {
    return op1 + op2;
}

#include <functional>
#include <stack>
#include <type_traits>

class Expression {
    public:
        struct F {
            F(const std::type_info& _type) : type(_type) {}
            virtual ~F() {}
            const std::type_info& type;
            virtual const void* f() = 0;
            virtual void d() = 0;
        };
    public:
        Expression() : m_cexpr(NULL) {}
        ~Expression() {
            if (m_cexpr) m_cexpr->d();
        }
        // function
        template<typename R, typename... T> void function(R (*f)(T...), void (*compile)(void (*)(), std::stack<F*>&)) {
            m_f = std::make_pair(reinterpret_cast<void (*)()>(f), compile);
        }
        template<typename R, typename T1, typename T2> static void f2compile(void (*f)(), std::stack<F*>& s) {
            auto opd2 = s.top();
            s.pop();
            auto opd1 = s.top();
            s.pop();
            s.push(new F2<R,T1,T2>(f, opd1, opd2));
        }
        void compile() {
            if (m_cexpr) m_cexpr->d();
            std::stack<F*> s;
            s.push(new Opd<double>(1));
            s.push(new Opd<double>(2));
            m_f.second(m_f.first, s);
            m_cexpr = s.top();
            s.pop();
            assert(s.empty());
        }
        void* execute() { 
            return const_cast<void*>(m_cexpr->f()); 
        }
        const std::type_info& type() { 
            return m_cexpr->type; 
        }
    private:
        F* m_cexpr;
        std::pair<void (*)(), void (*)(void (*)(), std::stack<F*>&)> m_f;
        template <typename T> struct Opd : F {
            Opd(T _opd) : F(typeid(T)), res(_opd) {}
            const void* f() { return &res; }
            T res;
            void d() { delete this; }
        };
        template <typename R, typename T1, typename T2> struct F2 : F {
            F2(void (*_fn)(), F* _opd1, F* _opd2) : F(typeid(R))
                , fn(reinterpret_cast<R(*)(T1,T2)>(_fn))
                , opd1(_opd1)
                , opd2(_opd2)
            {}
            const void* f() { res = fn(*(T1*) opd1->f(), *(T2*) opd2->f()); return &res; }
            F* opd1; F* opd2;
            R res; R (*fn)(T1,T2);
            void d() { opd1->d(); opd2->d(); delete this; }
        };
};

TEST_CASE("expression") {
    Expression e;
    e.function(foo, e.f2compile<double, double, double>);
    e.compile();
    e.execute();
    REQUIRE(e.type() == typeid(double));
    REQUIRE(*static_cast<double*>(e.execute()) == 3);
}

And my problem is how write better code c++11 using variadic template. How write a function "fNcompile" and a function "FN" with variadic template.

I don't think you need the variadic template. Instead:

template<typename R>
void fcompile(void (*f)()) {
    new F0<R>(reinterpret_cast<void (*)()>(f));
    ...
}
template<typename R, typename T>
void fcompile(void (*f)(T)) {
    new F1<R,T>(reinterpret_cast<void (*)()>(f));
    ...
}
template<typename R, typename T1, typename T2>
void fcompile(void (*f)(T1, T2)) {
    new F1<R,T1,T2>(reinterpret_cast<void (*)()>(f));
    ...
}

Now you can call fcompile<some_type>(some_func) for any some_type and any nullary/unary/binary some_func which returns void.

To answer the specific question, below are variadic FN and fNcompile as close as possible to your existing code. First, though, since you said you're working in C++11, we'll need an equivalent of std::make_index_sequence from C++14. Here's a simple one. You can search for others that are smarter about being less likely to hit compiler template limitations...

namespace cxx_compat {
    template <typename T, T... Values>
    struct integer_sequence {
        static constexpr std::size_t size() const
        { return sizeof...(Values); }
    };

    template <typename T, T Smallest, T... Values>
    struct make_integer_sequence_helper {
        static_assert(Smallest > 0,
            "make_integer_sequence argument must not be negative");
        using type = typename make_integer_sequence_helper<
            T, Smallest-1, Smallest-1, Values...>::type;
    };
    template <typename T, T... Values>
    struct make_integer_sequence_helper<T, 0, Values...> {
        using type = integer_sequence<T, Values...>;
    };

    template <typename T, T N>
    using make_integer_sequence =
        typename make_integer_sequence_helper<T, N>::type;

    template <std::size_t... Values>
    using index_sequence = integer_sequence<std::size_t, Values...>;

    template <std::size_t N>
    using make_index_sequence = make_integer_sequence<std::size_t, N>;

    template <typename... T>
    using index_sequence_for = make_index_sequence<sizeof...(T)>;
} // end namespace cxx_compat

And now, the actual FN and fNcompile :

template <typename R, typename ...T> struct FN : F {
private:
    template <typename T>
    using any_to_Fstar = F*;
public:
    FN(void (*_fn)(), any_to_Fstar<T> ... _opd) : F(typeid(R))
        , fn(reinterpret_cast<R(*)(T...)>(_fn))
        , opd{_opd...}
    {}
    FN(R (*_fn)(T...)) : F(typeid(R)), fn(_fn), opd() {}
    const void* f() {
        f_helper(cxx_compat::index_sequence_for<T...>{});
        return &res;
    }
    std::array<F*, sizeof...(T)> opd;
    R res; R (*fn)(T...);
    void d() {
        for (F* o : opd)
            o->d();
        delete this;
    }
private:
    template <std::size_t... Inds>
    void f_helper(cxx_compat::index_sequence<Inds...>)
    { res = fn(*(T*) opd[Inds]->f() ...); }
};

template<typename R, typename... T>
static void fNcompile(void (*f)(), std::stack<F*>& s) {
    auto* f_obj = new FN<R, T...>(f);
    for (std::size_t ind = sizeof...(T); ind > 0;) {
        f_obj->opd[--ind] = s.top();
        s.pop();
    }
    s.push(f_obj);
}

What's going on:

  • To actually call the function pointer, we need access to a number of function arguments at the same time, so to replace the named members opd1 , opd2 with a number of F* pointers determined by template instantiation, we use a std::array<F*, sizeof...(T)> , since sizeof...(T) is the number of argument types provided to the template.

  • For compatibility with the F2 constructor you declared, any_to_Fstar<T> ... _opd declares a number of constructor parameters to match the number of T template arguments, all with the same type F* . (But now fNcompile uses the additional constructor taking just the function pointer instead, and sets the array members afterward.)

  • To get at these pointers and pass them all to fn in one expression, we need to expand some sort of variadic pack. Here's where index_sequence comes in:

    • index_sequence_for<T...> is a type alias for index_sequence with a sequence of numbers counting up from zero as template arguments. For example, if sizeof...(T) is 4, then index_sequence_for<T...> is index_sequence<0, 1, 2, 3> .

    • f just calls a private function f_helper , passing it an object of that index_sequence_for<T...> type.

    • The compiler can deduce the template argument list for f_helper from matching the index_sequence types: Inds... must be that same sequence of numbers counting up from zero.

    • In the f_helper body, the expression fn(*(T*) opd[Inds]->f() ...) is instantiated by expanding both the template parameter packs T and Inds to get one list of function arguments for calling fn .

However, use of void pointers and reinterpret_cast is dangerous and rarely actually necessary in C++. There's almost always a safer way using templates. So I'd redesign this to be something more like:

#include <type_traits>
#include <typeinfo>
#include <stdexcept>
#include <memory>
#include <stack>

namespace cxx_compat {
    // Define integer_sequence and related templates as above.

    template <typename T, typename... Args>
    std::unique_ptr<T> make_unique(Args&& ... args)
    {
        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    }
} // end namespace cxx_compat

class bad_expression_type : public std::logic_error
{
public:
    bad_expression_type(const std::type_info& required,
                        const std::type_info& passed)
        : logic_error("bad_argument_type"),
          required_type(required),
          passed_type(passed) {}
    const std::type_info& required_type;
    const std::type_info& passed_type;
};

class Expression
{
public:
    class F
    {
    public:
        F() noexcept = default;
        F(const F&) = delete;
        F& operator=(const F&) = delete;
        virtual ~F() = default;
        virtual const std::type_info& type() const noexcept = 0;
        virtual void compile(std::stack<std::unique_ptr<F>>&) = 0;

        template <typename R>
        R call_R() const;
    };
    using F_ptr = std::unique_ptr<F>;
    using F_stack = std::stack<F_ptr>;

    template <typename R>
    class Typed_F : public F
    {
    public:
        const std::type_info& type() const noexcept override
        { return typeid(R); }
        virtual R call() const = 0;
    };

    // Accepts any callable: function pointer, lambda, std::function,
    // other class with operator().
    template <typename R, typename... T, typename Func,
        typename = typename std::enable_if<std::is_convertible<
            decltype(std::declval<const Func&>()(std::declval<T>()...)),
            R>::value>::type>
    void function(Func func)
    {
        store_func<R, T...>(std::move(func));
    }
    // Overload for function pointer that does not need explicit
    // template arguments:
    template <typename R, typename... T>
    void function(R (*fptr)(T...))
    {
        store_func<R, T...>(fptr);
    }

    template <typename T>
    void constant(const T& value)
    {
        store_func<T>([value](){ return value; });
    }

    void compile(F_stack& stack)
    {
        m_cexpr->compile(stack);
    }

private:
    template <typename Func, typename R, typename... T>
    class F_Impl : public Typed_F<R>
    {
    public:
        F_Impl(Func func) : m_func(std::move(func)) {}
        void compile(F_stack& stack) override {
            take_args_helper(stack, cxx_compat::index_sequence_for<T...>{});
        }
        R call() const override {
            return call_helper(cxx_compat::index_sequence_for<T...>{});
        }
    private:
        template <typename Arg>
        int take_one_arg(std::unique_ptr<Typed_F<Arg>>& arg, F_stack& stack)
        {
            auto* fptr = dynamic_cast<Typed_F<Arg>*>(stack.top().get());
            if (!fptr)
                throw bad_expression_type(
                    typeid(Arg), stack.top()->type());
            arg.reset(fptr);
            stack.top().release();
            stack.pop();
            return 0;
        }
        template <std::size_t... Inds>
        void take_args_helper(F_stack& stack, cxx_compat::index_sequence<Inds...>)
        {
            using int_array = int[];
            (void) int_array{ take_one_arg(std::get<Inds>(m_args), stack) ..., 0 };
        }
        template <std::size_t... Inds>
        R call_helper(cxx_compat::index_sequence<Inds...>) const {
            return m_func(std::get<Inds>(m_args)->call()...);
        }

        Func m_func;
        std::tuple<std::unique_ptr<Typed_F<T>>...> m_args;
    };

    template <typename R, typename... T, typename Func>
    void store_func(Func func)
    {
        m_cexpr = cxx_compat::make_unique<F_Impl<Func, R, T...>>(
            std::move(func));
    }

    F_ptr m_cexpr;
};

template <typename R>
R Expression::F::call_R() const
{
    auto* typed_this = dynamic_cast<const Typed_F<R>*>(this);
    if (!typed_this)
        throw bad_expression_type(typeid(R), type());
    return typed_this->call();
}

TEST_CASE("expression") {
    Expression a;
    a.constant(1.0);
    Expression b;
    b.constant(2.0);
    Expression c;
    c.function(+[](double x, double y) { return x+y; });

    Expression::F_stack stack;
    a.compile(stack);
    REQUIRE(stack.size() == 1);
    b.compile(stack);
    REQUIRE(stack.size() == 2);
    c.compile(stack);
    REQUIRE(stack.size() == 1);

    REQUIRE(stack.top() != nullptr);
    REQUIRE(stack.top()->type() == typeid(double));
    REQUIRE(stack.top()->call_R<double>() == 3.0);
}

It would also be possible, but a bit tricky, to support reference and const variations of the argument and result types, for example, using a std::string(*)() function as an argument to an unsigned int(*)(const std::string&) function.

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