属性系统基于std::any:模板类型推导
Property system based on std::any: template type deduction
为了实现多态对象的属性系统,我首先声明了以下结构:
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;
};
因此 属性 定义了类型、名称和访问权限(只读、只写或读写)。然后我开始 属性 class 如下:
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;
属性 与基类型相关联,但可能(并且将会)填充与派生类型的实例相关联的访问器。访问器将封装在 Base
类型的实例上访问 std::any
对象的函数。 get
和 set
方法声明如下(此处未显示类型检查以使代码最少):
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_)
);
}
};
现在,考虑这些虚拟 classes。
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: note: template argument deduction/substitution failed:
prog.cc:149:50: note: mismatched types std::function<void(Type&, const Value&)>
and int (bar::*)() const
property<foo> p_i{"bar_i", &bar::get_i, set_i};
有没有办法在保留代码的同时解决这个问题"simple"?
完整的实例可用here。
在这个答案的最后是一个略有不同的方法。不过,我将从一般问题开始。
问题是 &bar::get_i
是一个指向成员函数的函数指针,而您的别名正在创建一个需要 class 作为附加模板参数的函数对象。
一些例子:
非成员函数:
#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 对象还需要基础 class(就像您对别名声明所做的那样)
你需要一个std::function<void(A,int)>
虽然你不能让你的例子变得更好。
一种使它 "bit" 比您的示例更容易的方法可能是使用 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;
}
并且您的每个 "derived" classes 都派生自 funcPtr
class,其中 "auto generates" 特定的别名声明。
std::function
是类型擦除类型。类型擦除类型不适合推导。
template<typename Derived, typename T>
using get_t = function<T(const Derived&)>;
get_t
是类型擦除类型的别名。同上。
创建特征 classes:
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
构造函数:
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
通过集合,并使用特征 class 来提取我们关心的类型。
这里会有很多工作要做。但它是一次写入工作。
在这些情况下我的首选语法是:
std::cout << (f->*p_i)();
和
(f->*p_i)(7);
其中 属性 充当成员函数指针,甚至
(f->*p_i) = 7;
std::cout << (f->*p_i);
其中 属性 透明地充当成员变量指针。
在这两种情况下,通过 ->*
的重载,在第二种情况下,通过从 ->*
.
返回伪引用
为了实现多态对象的属性系统,我首先声明了以下结构:
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;
};
因此 属性 定义了类型、名称和访问权限(只读、只写或读写)。然后我开始 属性 class 如下:
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;
属性 与基类型相关联,但可能(并且将会)填充与派生类型的实例相关联的访问器。访问器将封装在 Base
类型的实例上访问 std::any
对象的函数。 get
和 set
方法声明如下(此处未显示类型检查以使代码最少):
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_)
);
}
};
现在,考虑这些虚拟 classes。
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: note: template argument deduction/substitution failed:
prog.cc:149:50: note: mismatched types
std::function<void(Type&, const Value&)>
andint (bar::*)() const
property<foo> p_i{"bar_i", &bar::get_i, set_i};
有没有办法在保留代码的同时解决这个问题"simple"?
完整的实例可用here。
在这个答案的最后是一个略有不同的方法。不过,我将从一般问题开始。
问题是 &bar::get_i
是一个指向成员函数的函数指针,而您的别名正在创建一个需要 class 作为附加模板参数的函数对象。
一些例子:
非成员函数:
#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 对象还需要基础 class(就像您对别名声明所做的那样)
你需要一个std::function<void(A,int)>
虽然你不能让你的例子变得更好。
一种使它 "bit" 比您的示例更容易的方法可能是使用 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;
}
并且您的每个 "derived" classes 都派生自 funcPtr
class,其中 "auto generates" 特定的别名声明。
std::function
是类型擦除类型。类型擦除类型不适合推导。
template<typename Derived, typename T>
using get_t = function<T(const Derived&)>;
get_t
是类型擦除类型的别名。同上。
创建特征 classes:
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
构造函数:
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
通过集合,并使用特征 class 来提取我们关心的类型。
这里会有很多工作要做。但它是一次写入工作。
在这些情况下我的首选语法是:
std::cout << (f->*p_i)();
和
(f->*p_i)(7);
其中 属性 充当成员函数指针,甚至
(f->*p_i) = 7;
std::cout << (f->*p_i);
其中 属性 透明地充当成员变量指针。
在这两种情况下,通过 ->*
的重载,在第二种情况下,通过从 ->*
.