键入擦除和一种模板方法模式

时间:2017-02-09 23:33:10

标签: c++ templates software-design type-erasure

考虑以下最小例子:

struct S {
    using func_t = void(*)(void *);

    template<typename T>
    static void proto(void *ptr) {
        static_cast<T*>(ptr)->f();
    }

    func_t func;
    void *ptr;
};

struct T {
    void f() {}
};

void g(S &s) {
    s.func(s.ptr);
}

int main() {
    T t;
    S s;
    s.func = &S::proto<T>;
    s.ptr = &t;
    g(s);
}

非常明显的想法是删除一堆对象的类型(如T,这不是唯一可用的类型)来创建S的实例数组,然后迭代它数组并调用预定的成员函数。

到目前为止,它很容易实现并且有效 现在我想提供一个在擦除对象上调用的外部函数,这可能是这样的:

template<typename T, typename F>
static void proto(void *ptr, F &&f) {
    auto *t = static_cast<T*>(ptr);
    std::forward<F>(f)(*t);
    t->f();
}

或者这个:

template<typename T>
static void proto(void *ptr, void(*f)(T &)) {
    auto *t = static_cast<T*>(ptr);
    f(*t);
    t->f();
}

被调用为:

s.func(s.ptr, [](auto obj){ /* ... */ });

一种模板方法模式,其中额外的功能由调用者而不是派生类提供 不幸的是,我无法做到这一点,因为我不能将专门化减少到可以分配给函数指针的同类。

我能看到的唯一选择是定义一个自定义类,如下所示:

struct C {
    template<typename T>
    void f(T &t) { /* ... */ }

    // ...
};

f以某种方式调用内部调用正确的成员函数,然后将其用作:

struct S {
    using func_t = void(*)(void *, C &);

    template<typename T>
    static void proto(void *ptr, C &c) {
        auto t = static_cast<T*>(ptr);
        c.f(*t);
        t->f();
    }

    func_t func;
    void *ptr;
};

这与我使用lambda所做的相差不远,但它更冗长,需要我明确声明类C

是否还有其他有效的替代方法可以实现相同目标,还是唯一可行的解​​决方案?

1 个答案:

答案 0 :(得分:0)

假设您可以枚举您希望支持的类型,您可以这样做:

#include <iostream>
#include <string>
#include <vector>

template <class... Ts>
struct PseudoFunction {
    private:
    template <class T>
    static void (*function)(T &);

    template <class T>
    static void call_func(void *object) {
        return function<T>(*static_cast<T *>(object));
    }

    template <class Fun>
    static void assign(Fun) {}

    template <class Fun, class Head, class... Tail>
    static void assign(Fun fun) {
        function<Head> = fun;
        assign<Fun, Tail...>(fun);
    }

    public:
    template <class T>
    PseudoFunction(T *t)
        : object(t)
        , func(call_func<T>) {}

    template <class F>
    static void set_function(F f) {
        assign<F, Ts...>(f);
    }
    void operator()() {
        func(object);
    }

    private:
    void *object;
    void (*func)(void *);
};

template <class... Ts>
template <class T>
void (*PseudoFunction<Ts...>::function)(T &) = nullptr;

//example types that are not related and not copy constructible
//but have the same member function name and signature
struct T1 {
    T1() = default;
    T1(const T1 &) = delete;
    void func(double d) {
        std::cout << "T1: " + std::to_string(d) + '\n';
    }
};

struct T2 {
    T2() = default;
    T2(const T2 &) = delete;
    void func(double d) {
        std::cout << "T2: " + std::to_string(d) + '\n';
    }
};

int main() {
    T1 t1;
    T2 t2;

    using PF = PseudoFunction<T1, T2>;

    std::vector<PF> funcs;
    funcs.push_back(&t1);
    funcs.push_back(&t2);

    PF::set_function([](auto &object) { object.func(3.14); });
    for (auto &f : funcs) {
        f();
    }
}

demo

它具有不错的调用语法(只需要在调用对象之前指定函数)以及设置可能未使用的函数指针的一些开销。

有人可能会创建一个包装器来执行set_function并一次性迭代PF