struct/class 的递归成员指针的可变序列作为模板参数
Variadic sequence of pointer to recursive member of struct/class as template parameter
我正在为一些模板编程而苦苦挣扎,我希望你能给我一些帮助。我编写了一个 C++11 接口,给定了一些结构,例如:
struct Inner{
double a;
};
struct Outer{
double x, y, z, r;
Inner in;
};
实现一个getter/setter到自定义为指定结构成员的真实数据:
MyData<Outer, double, &Outer::x,
&Outer::y,
&Outer::z,
&Outer::in::a //This one is not working
> state();
Outer foo = state.get();
//...
state.set(foo);
我设法通过以下方式为简单结构实现了这一点:
template <typename T, typename U, U T::* ... Ms>
class MyData{
std::vector<U *> var;
public:
explicit MyData();
void set(T const& var_);
T get() const;
};
template <typename T, typename U, U T::* ... Ms>
MyData<T, U, Ms ... >::Struct():var(sizeof...(Ms))
{
}
template <typename T, typename U, U T::* ... Ms>
void MyData<T, U, Ms ...>::set(T const& var_){
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
*var[i++] = var_.*d;
}
}
template <typename T, typename U, U T::* ... Ms>
T MyData<T, U, Ms ...>::get() const{
T var_;
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
var_.*d = *var[i++];
}
return var_;
}
但是当我传递一个嵌套结构的成员时它失败了。理想情况下,我想实现一个指向成员类型的通用指针,它允许我与多个级别的范围解析兼容。我找到了 this approach,但我不确定这是否适用于我的问题,或者是否存在一些可供使用的实现。提前致谢!
相关帖子:
Pointer to inner struct
您可以将成员指针包装到结构中以允许更轻松的链接:
template <typename...> struct Accessor;
template <typename T, typename C, T (C::*m)>
struct Accessor<std::integral_constant<T (C::*), m>>
{
const T& get(const C& c) { return c.*m; }
T& get(C& c) { return c.*m; }
};
template <typename T, typename C, T (C::*m), typename ...Ts>
struct Accessor<std::integral_constant<T (C::*), m>, Ts...>
{
auto get(const C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
auto get(C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
};
template <typename T, typename U, typename ...Ts>
class MyData
{
std::vector<U> vars{sizeof...(Ts)};
template <std::size_t ... Is>
T get(std::index_sequence<Is...>) const
{
T res;
((Ts{}.get(res) = vars[Is]), ...); // Fold expression C++17
return res;
}
template <std::size_t ... Is>
void set(std::index_sequence<Is...>, T const& t)
{
((vars[Is] = Ts{}.get(t)), ...); // Fold expression C++17
}
public:
MyData() = default;
T get() const { return get(std::index_sequence_for<Ts...>()); }
void set(const T& t) { return set(std::index_sequence_for<Ts...>(), t); }
};
用法类似于
template <auto ...ms> // C++17 too
using Member = Accessor<std::integral_constant<decltype(ms), ms>...>;
MyData<Outer, double, Member<&Outer::x>,
Member<&Outer::y>,
Member<&Outer::z>,
Member<&Outer::in, &Inner::a>
> state;
std::index_sequence
是 C++14,但可以在 C++11 中实现。
C++17 中的折叠表达式也可以在 C++11 中模拟。
typename <auto>
(C++17) 应替换为 typename <typename T, T value>
。
我会使用 lambda 方法在 C++17 中实现类似的功能(C++14 也可以,只需更改折叠表达式):
auto access_by() {
return [] (auto &&t) -> decltype(auto) {
return decltype(t)(t);
};
}
template<class Ptr0, class... Ptrs>
auto access_by(Ptr0 ptr0, Ptrs... ptrs) {
return [=] (auto &&t) -> decltype(auto) {
return access_by(ptrs...)(decltype(t)(t).*ptr0);
};
}
auto data_assigner_from = [] (auto... accessors) {
return [=] (auto... data) {
return [accessors..., data...] (auto &&t) {
((accessors(decltype(t)(t)) = data), ...);
};
};
};
让我们看看如何使用这些 lambda:
struct A {
int x, y;
};
struct B {
A a;
int z;
};
access_by
函数可以像这样使用:
auto bax_accessor = access_by(&B::a, &A::x);
auto bz_accessor = access_by(&B::z);
那么对于B b;
,bax_accessor(b)
就是b.a.x
; bz_accessor(b)
是 b.z
。值类别也被保留,因此您可以分配:bax_accessor(b) = 4
.
data_assigner_from()
将构造一个分配器来分配具有给定访问器的 B
实例:
auto data_assigner = data_assigner_from(
access_by(&B::a, &A::x),
access_by(&B::z)
);
data_assigner(12, 3)(b);
assert(b.z == 3 && b.a.x == 12);
成员指针的泛化是一个可以在编译时将T
映射到X&
的函数。
在 c++17 it isn't hard to wire things up thanks to auto
. In c++11 中变得更难了。但基本思想是你实际上并没有传递成员指针,你传递的是类型,而那些类型知道如何获取你的 class 并从中获取引用。
template<class T, class D, class...Fs>
struct MyData {
std::array<D*, sizeof...(Fs)> var = {};
explicit MyData()=default;
void set(T const& var_) {
var = {{ Fs{}(std::addressof(var_))... }};
}
T get() {
T var_;
std::size_t index = 0;
using discard=int[];
(void)discard{ 0, (void(
*Fs{}(std::addressof(var_)) = *var[index++]
),0)... };
return var_;
}
};
它仍然需要编写一个实用程序,使编写 Fs...
成员指针案例变得容易
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M > {
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
#define TYPE_N_VAL(...) \
decltype(__VA_ARGS__), __VA_ARGS__
#define MEM_PTR(...) get_ptr_to_member_t< TYPE_N_VAL(__VA_ARGS__) >
现在基本情况是
MyData< Outer, double, MEM_PTR(&Outer::x), MEM_PTR(&Outer::y) >
现在可以处理更复杂的情况。
一种方法是教 get_ptr_to_member
作曲。这是一项烦人的工作,但不是最基本的。安排是为了 decltype(ptr_to_member_t * ptr_to_member_t)
returns 一个实例正确的类型,应用它,然后获取该指针并在其左侧运行。
template<class First, class Second>
struct composed;
template<class D>
struct composes {};
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
template<class First, class Second>
struct composed:composes<composed<First, Second>> {
template<class In>
auto operator()(In&& in) const
RETURNS( Second{}( First{}( std::forward<In>(in) ) ) )
};
template<class First, class Second>
composed<First, Second> operator*( composes<Second> const&, composes<First> const& ) {
return {};
}
然后我们升级:
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M >:
composes<get_ptr_to_member_t< D T::*, M >>
{
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
现在 *
组成了它们。
MyData<TestStruct, double, MEM_PTR(&Outer::x),
MEM_PTR(&Outer::y),
MEM_PTR(&Outer::z),
decltype(MEM_PTR(&Inner::a){} * MEM_PTR(&Outer::in){})
> state();
答案可能有很多拼写错误,但设计合理。
在 c++17 中,大部分垃圾都蒸发了,就像宏一样。
我正在为一些模板编程而苦苦挣扎,我希望你能给我一些帮助。我编写了一个 C++11 接口,给定了一些结构,例如:
struct Inner{
double a;
};
struct Outer{
double x, y, z, r;
Inner in;
};
实现一个getter/setter到自定义为指定结构成员的真实数据:
MyData<Outer, double, &Outer::x,
&Outer::y,
&Outer::z,
&Outer::in::a //This one is not working
> state();
Outer foo = state.get();
//...
state.set(foo);
我设法通过以下方式为简单结构实现了这一点:
template <typename T, typename U, U T::* ... Ms>
class MyData{
std::vector<U *> var;
public:
explicit MyData();
void set(T const& var_);
T get() const;
};
template <typename T, typename U, U T::* ... Ms>
MyData<T, U, Ms ... >::Struct():var(sizeof...(Ms))
{
}
template <typename T, typename U, U T::* ... Ms>
void MyData<T, U, Ms ...>::set(T const& var_){
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
*var[i++] = var_.*d;
}
}
template <typename T, typename U, U T::* ... Ms>
T MyData<T, U, Ms ...>::get() const{
T var_;
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
var_.*d = *var[i++];
}
return var_;
}
但是当我传递一个嵌套结构的成员时它失败了。理想情况下,我想实现一个指向成员类型的通用指针,它允许我与多个级别的范围解析兼容。我找到了 this approach,但我不确定这是否适用于我的问题,或者是否存在一些可供使用的实现。提前致谢!
相关帖子:
Pointer to inner struct
您可以将成员指针包装到结构中以允许更轻松的链接:
template <typename...> struct Accessor;
template <typename T, typename C, T (C::*m)>
struct Accessor<std::integral_constant<T (C::*), m>>
{
const T& get(const C& c) { return c.*m; }
T& get(C& c) { return c.*m; }
};
template <typename T, typename C, T (C::*m), typename ...Ts>
struct Accessor<std::integral_constant<T (C::*), m>, Ts...>
{
auto get(const C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
auto get(C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
};
template <typename T, typename U, typename ...Ts>
class MyData
{
std::vector<U> vars{sizeof...(Ts)};
template <std::size_t ... Is>
T get(std::index_sequence<Is...>) const
{
T res;
((Ts{}.get(res) = vars[Is]), ...); // Fold expression C++17
return res;
}
template <std::size_t ... Is>
void set(std::index_sequence<Is...>, T const& t)
{
((vars[Is] = Ts{}.get(t)), ...); // Fold expression C++17
}
public:
MyData() = default;
T get() const { return get(std::index_sequence_for<Ts...>()); }
void set(const T& t) { return set(std::index_sequence_for<Ts...>(), t); }
};
用法类似于
template <auto ...ms> // C++17 too
using Member = Accessor<std::integral_constant<decltype(ms), ms>...>;
MyData<Outer, double, Member<&Outer::x>,
Member<&Outer::y>,
Member<&Outer::z>,
Member<&Outer::in, &Inner::a>
> state;
std::index_sequence
是 C++14,但可以在 C++11 中实现。
C++17 中的折叠表达式也可以在 C++11 中模拟。
typename <auto>
(C++17) 应替换为 typename <typename T, T value>
。
我会使用 lambda 方法在 C++17 中实现类似的功能(C++14 也可以,只需更改折叠表达式):
auto access_by() {
return [] (auto &&t) -> decltype(auto) {
return decltype(t)(t);
};
}
template<class Ptr0, class... Ptrs>
auto access_by(Ptr0 ptr0, Ptrs... ptrs) {
return [=] (auto &&t) -> decltype(auto) {
return access_by(ptrs...)(decltype(t)(t).*ptr0);
};
}
auto data_assigner_from = [] (auto... accessors) {
return [=] (auto... data) {
return [accessors..., data...] (auto &&t) {
((accessors(decltype(t)(t)) = data), ...);
};
};
};
让我们看看如何使用这些 lambda:
struct A {
int x, y;
};
struct B {
A a;
int z;
};
access_by
函数可以像这样使用:
auto bax_accessor = access_by(&B::a, &A::x);
auto bz_accessor = access_by(&B::z);
那么对于B b;
,bax_accessor(b)
就是b.a.x
; bz_accessor(b)
是 b.z
。值类别也被保留,因此您可以分配:bax_accessor(b) = 4
.
data_assigner_from()
将构造一个分配器来分配具有给定访问器的 B
实例:
auto data_assigner = data_assigner_from(
access_by(&B::a, &A::x),
access_by(&B::z)
);
data_assigner(12, 3)(b);
assert(b.z == 3 && b.a.x == 12);
成员指针的泛化是一个可以在编译时将T
映射到X&
的函数。
在 c++17 it isn't hard to wire things up thanks to auto
. In c++11 中变得更难了。但基本思想是你实际上并没有传递成员指针,你传递的是类型,而那些类型知道如何获取你的 class 并从中获取引用。
template<class T, class D, class...Fs>
struct MyData {
std::array<D*, sizeof...(Fs)> var = {};
explicit MyData()=default;
void set(T const& var_) {
var = {{ Fs{}(std::addressof(var_))... }};
}
T get() {
T var_;
std::size_t index = 0;
using discard=int[];
(void)discard{ 0, (void(
*Fs{}(std::addressof(var_)) = *var[index++]
),0)... };
return var_;
}
};
它仍然需要编写一个实用程序,使编写 Fs...
成员指针案例变得容易
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M > {
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
#define TYPE_N_VAL(...) \
decltype(__VA_ARGS__), __VA_ARGS__
#define MEM_PTR(...) get_ptr_to_member_t< TYPE_N_VAL(__VA_ARGS__) >
现在基本情况是
MyData< Outer, double, MEM_PTR(&Outer::x), MEM_PTR(&Outer::y) >
现在可以处理更复杂的情况。
一种方法是教 get_ptr_to_member
作曲。这是一项烦人的工作,但不是最基本的。安排是为了 decltype(ptr_to_member_t * ptr_to_member_t)
returns 一个实例正确的类型,应用它,然后获取该指针并在其左侧运行。
template<class First, class Second>
struct composed;
template<class D>
struct composes {};
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
template<class First, class Second>
struct composed:composes<composed<First, Second>> {
template<class In>
auto operator()(In&& in) const
RETURNS( Second{}( First{}( std::forward<In>(in) ) ) )
};
template<class First, class Second>
composed<First, Second> operator*( composes<Second> const&, composes<First> const& ) {
return {};
}
然后我们升级:
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M >:
composes<get_ptr_to_member_t< D T::*, M >>
{
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
现在 *
组成了它们。
MyData<TestStruct, double, MEM_PTR(&Outer::x),
MEM_PTR(&Outer::y),
MEM_PTR(&Outer::z),
decltype(MEM_PTR(&Inner::a){} * MEM_PTR(&Outer::in){})
> state();
答案可能有很多拼写错误,但设计合理。
在 c++17 中,大部分垃圾都蒸发了,就像宏一样。