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.