基于std :: any的属性系统:模板类型推导

时间:2016-09-09 11:57:29

标签: c++ c++17

为了实现多态对象的属性系统,我首先声明了以下结构:

enum class access_rights_t
{
    NONE        = 0,
    READ        = 1 << 0,
    WRITE       = 1 << 1,
    READ_WRITE  = READ | WRITE
};

struct property_format
{
    type_index type;

    string name;

    access_rights_t access_rights;
};

因此,使用类型,名称和访问权限(只读,只写或读写)定义属性。然后我按如下方式启动了属性类:

template<typename Base>
class property : property_format
{

public:

    template<typename Derived, typename T>
    using get_t = function<T(const Derived&)>;

    template<typename Derived, typename T>
    using set_t = function<void(Derived&, const T&)>;

private:

    get_t<Base, any> get_f;
    set_t<Base, any> set_f;

该属性与基类型相关联,但可以(并且将)填充与派生类型的实例关联的访问器。访问器将使用在std::any类型的实例上访问Base个对象的函数进行封装。 getset方法声明如下(此处未显示类型检查以使代码最小化):

public:

    template<typename T>
    T get(const Base& b) const
    {
        return any_cast<T>(this->get_f(b));
    }

    template<typename T>
    void set(Base& b, const T& value_)
    {
        this->set_f(b, any(value_));
    }

然后构造函数(访问权限设置为NONE以使代码最小化):

    template<typename Derived, typename T>
    property(
        const string& name_,
        get_t<Derived, T> get_,
        set_t<Derived, T> set_ = nullptr
    ):
        property_format{
            typeid(T),
            name_,
            access_rights_t::NONE
        },
        get_f{caller<Derived, T>{get_}},
        set_f{caller<Derived, T>{set_}}
    {
    }

    template<typename Derived, typename T>
    property(
        const string& name_,
        set_t<Derived, T> set_
    ):
        property{
            name_,
            nullptr,
            set_
        }
    {
    }

作为参数传递的函数通过辅助结构caller封装:

private:

    template<typename Derived, typename T>
    struct caller
    {
        get_t<Derived, T> get_f;
        set_t<Derived, T> set_f;

        caller(get_t<Derived, T> get_):
            get_f{get_}
        {
        }

        caller(set_t<Derived, T> set_):
            set_f{set_}
        {
        }

        any operator()(const Base& object_)
        {
            return any{
                this->get_f(
                    static_cast<const Derived&>(object_)
                )
            };
        }

        void operator()(Base& object_, const any& value_)
        {
            this->set_f(
                static_cast<Derived&>(object_),
                any_cast<Value>(value_)
            );
        }
    };

现在,考虑这些虚拟类。

struct foo
{
};

struct bar : foo
{
    int i, j;

    bar(int i_, int j_):
        i{i_},
        j{j_}
    {
    }

    int get_i() const {return i;}

    void set_i(const int& i_) { this->i = i_; }
};

我可以写下面的代码:

int main()
{
    // declare accessors through bar methods

    property<foo>::get_t<bar, int> get_i = &bar::get_i;
    property<foo>::set_t<bar, int> set_i = &bar::set_i;

    // declare a read-write property

    property<foo> p_i{"bar_i", get_i, set_i};

    // declare a getter through a lambda

    property<foo>::get_t<bar, int> get_j = [](const bar& b_){ return b_.j; };

    // declare a read-only property

    property<foo> p_j{"bar_j", get_j};

    // dummy usage

    bar b{42, 24};
    foo& f = b;

    cout << p_i.get<int>(f) << " " << p_j.get<int>(f) << endl;
    p_i.set<int>(f, 43);
    cout << p_i.get<int>(f) << endl;
}

我的问题是模板类型推导不允许我声明一个属性直接将访问器作为参数传递,如:

property<foo> p_i{"bar_i", &bar::get_i, &bar::set_i};

产生以下错误:

  

prog.cc:62:5:注意:模板参数扣除/替换失败:

     

prog.cc:149:50:注意:不匹配的类型std::function<void(Type&, const Value&)>int (bar::*)() const

property<foo> p_i{"bar_i", &bar::get_i, set_i};

有没有办法解决这个问题,同时保持代码“简单”?

可以使用完整的实时示例here

2 个答案:

答案 0 :(得分:1)

std::function是类型擦除类型。类型擦除类型不适合扣除。

template<typename Derived, typename T>
using get_t = function<T(const Derived&)>;

get_t是类型擦除类型的别名。同上。

创建特征类:

template<class T>
struct gettor_traits : std::false_type {};

这将告诉您T是否是有效的gettor,如果是,它的输入和输出类型是什么。同样适用于settor_traits

所以

 template<class T, class Derived>
 struct gettor_traits< std::function<T(Derived const&)> >:
   std::true_type
 {
   using return_type = T;
   using argument_type = Derived;
 }; 
 template<class T, class Derived>
 struct gettor_traits< T(Derived::*)() >:
   std::true_type
 {
   using return_type = T;
   using argument_type = Derived;
 }; 

现在我们回到了property ctor:

template<class Gettor,
  std::enable_if_t< gettor_traits<Gettor>{}, int> =0,
  class T = typename gettor_traits<Gettor>::return_value,
  class Derived = typename gettor_traits<Gettor>::argument_type
>
property(
    const string& name_,
    Gettor get_
):
    property_format{
        typeid(T),
        name_,
        access_rights_t::NONE
    },
    get_f{caller<Derived, T>{get_}},
    nullptr
{
}

我们使用SFINAE确保我们的Gettor通过集合,并使用特征类来提取我们关心的类型。

这里有很多工作要做。但这是一次性写作。

在这些情况下我的首选语法是:

std::cout << (f->*p_i)();

(f->*p_i)(7);

其中属性的作用类似于成员函数指针,甚至

(f->*p_i) = 7;
std::cout << (f->*p_i);

其中属性透明地表现为成员变量指针。

在这两种情况下,通过->*的重载,在第二种情况下,通过从->*返回伪引用。

答案 1 :(得分:0)

在这个答案的最后是一个略有不同的方法。我将从一般问题开始。

问题是&bar::get_i是一个指向成员函数的函数指针,而您的别名正在创建一个需要该类作为附加模板参数的函数对象。 一些例子:

非会员功能:

#include <functional>

void a(int i) {};

void f(std::function<void(int)> func)
{
}

int main()
{
  f(&a);
  return 0;
}

这很好用。现在,如果我将a更改为结构:

#include <functional>

struct A
{
void a(int i) {};
};

void f(std::function<void(int)> func)
{
}


int main()
{
  f(std::function<void(int)>(&A::a));
  return 0;
}

这会收到错误:

error: no matching function for call to std::function<void(int)>::function(void (A::*)(int))'

因为std :: function对象也需要基类(与别名声明一样)

您需要std::function<void(A,int)>

你不能让你的榜样好得多。

使用CRTP的方法可能是使这种方法比你的例子更“容易”。

#include <functional>

template <typename Class>
struct funcPtr
{
  template <typename type>
  using fun = std::function<void(Class,type)>;
};

struct A : public funcPtr<A>
{
void a(int i) {};
};

void f(A::fun<int> func)
{
};


int main()
{
  f(A::fun<int>(&A::a));
  return 0;
}

每个“派生”类派生自一个funcPtr类,它“自动生成”特定的别名声明。