9

I'm struggling with some template programming and I hope you can give me some help. I coded a C++11 interface that, given some structs like:

struct Inner{
  double a;
};
struct Outer{
  double x, y, z, r;
  Inner in;
};

Implements a getter/setter to the real data that is customized to the specified struct members:

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);

I managed to implement this for simple structs in the following way:

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_;
}

But it fails when I pass a member of a nested struct. Ideally, I'd like to implement a generic pointer to member type that allows me to be compatible with several levels of scope resolutions. I found this approach, but I'm not sure if this should be applied to my problem or if there exists some implementation ready to use. Thanks in advance!

Related posts:

Implicit template parameters

Pointer to inner struct

3 Answers3

7

You might wrap member pointer into struct to allow easier chaining:

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); }

};

With usage similar to

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 is C++14 but can be implemented in C++11.
Folding expression from C++17 can be simulated too in C++11.
typename <auto> (C++17) should be replaced by typename <typename T, T value>.

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Since I expect to have multiple levels of scope resolution, Is it possible to wrap `Member<&Outer::in, &Inner::a>` to `Member<&Outer::in::a>` ? Thanks in advance :) – Manuel Castillo-Lopez Nov 27 '18 at 13:52
  • `&Outer::in::a` is invalid unfortunately (currently it is parsed as member `a` of **inner class** `Outer::in`, but `Outer::in` is not a class, but a member). So you have to split it. MACRO would allow to write it `MEMBER(Outer, in, a)`. – Jarod42 Nov 27 '18 at 14:09
3

A generalization of a member pointer is a function that can map T to X& at compile time.

In it isn't hard to wire things up thanks to auto. In it gets harder. But the basic idea is that you don't actually pass member pointers, you pass types, and those types know how to take your class and get a reference out of them.

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_;
  }
};

it remains to write a utility that makes writing the Fs... easy for the member pointer case

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__) >

now the basic case is

MyData< Outer, double, MEM_PTR(&Outer::x), MEM_PTR(&Outer::y) >

The more complex case can now be handled.

An approach would be to teach get_ptr_to_member to compose. This is annoying work, but nothing fundamental. Arrange is so that decltype(ptr_to_member_t * ptr_to_member_t) returns a type that instances right, applies it, then takes that pointer and runs the left hand side on it.

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 {};
}

then we upgrade:

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 );
  }
};

and now * composes them.

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();

answre probably contains many typos, but design is sound.

In most of the garbage evaporates, like the macros.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

I would use lambda approach to implement similar functionalities in C++17(C++14 is also ok, just change the fold expression):

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), ...);
        };
    };
};

Let's see how to use these lambdas:

struct A {
    int x, y;
};

struct B {
    A a;
    int z;
};

access_by function can be used like:

auto bax_accessor = access_by(&B::a, &A::x);
auto bz_accessor = access_by(&B::z);

Then for B b;, bax_accessor(b) is b.a.x; bz_accessor(b) is b.z. Value category is also preserved, so you can assign: bax_accessor(b) = 4.

data_assigner_from() will construct an assigner to assign a B instance with given accessors:

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);
llllllllll
  • 16,169
  • 4
  • 31
  • 54