Member pointer to array element

前端 未结 6 1224
野的像风
野的像风 2021-02-04 06:29

It\'s possible to define a pointer to a member and using this later on:

struct foo
{
  int a;
  int b[2];
};

int main() {
foo bar; int foo::*

相关标签:
6条回答
  • 2021-02-04 06:37

    2020 update, with actual solution:

    • The Standard does currently not specify any way to actually work with the member pointers in a way that would allow arithmetics or anything to get the pointer to the "inner" array element
    • OTOH, the standard library now has all the necessities to patch the appropriate member pointer class yourself, even with the array element access.

    First, the member pointers are usually implemented as "just offsets", although quite scary. Let's see an example (on g++9, arch amd64):

    struct S { int a; float b[10]; };
    
    float(S::*mptr)[10] = &S::b;
    *reinterpret_cast<uintptr_t *>(&mptr)  //this is 4
    
    int S::*iptr = &S::a;
    *reinterpret_cast<uintptr_t *>(&iptr)  //this is 0
    
    iptr = nullptr;
    *reinterpret_cast<uintptr_t *>(&iptr)  //this seems to be 18446744073709551615 on my box
    

    Instead you can make a bit of a wrapper (it's quite long but I didn't want to remove the convenience operators):

    #include <type_traits>
    
    template<class M, typename T>
    class member_ptr
    {
        size_t off_;
    public:
        member_ptr() : off_(0) {}
        member_ptr(size_t offset) : off_(offset) {}
    
        /* member access */
        friend const T& operator->*(const M* a, const member_ptr<M, T>& p)
        { return (*a)->*p; }
        friend T& operator->*(M* a, const member_ptr<M, T>& p)
        { return (*a)->*p; }
    
        /* operator.* cannot be overloaded, so just take the arrow again */
        friend const T& operator->*(const M& a, const member_ptr<M, T>& p)
        { return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(&a) + p.off_); }
        friend T& operator->*(M& a, const member_ptr<M, T>& p)
        { return *reinterpret_cast<T*>(reinterpret_cast<char*>(&a) + p.off_); }
    
        /* convert array access to array element access */
        member_ptr<M, typename std::remove_extent<T>::type> operator*() const
        { return member_ptr<M, typename std::remove_extent<T>::type>(off_); }
    
        /* the same with offset right away */
        member_ptr<M, typename std::remove_extent<T>::type> operator[](size_t offset) const
        { return member_ptr<M, typename std::remove_extent<T>::type>(off_)+offset; }
    
        /* some operators */
        member_ptr& operator++()
        { off_ += sizeof(T); return *this; };
        member_ptr& operator--()
        { off_ -= sizeof(T); return *this; };
        member_ptr operator++(int)
        { member_ptr copy; off_ += sizeof(T); return copy; };
        member_ptr operator--(int)
        { member_ptr copy; off_ -= sizeof(T); return copy; };
    
        member_ptr& operator+=(size_t offset)
        { off_ += offset * sizeof(T); return *this; }
        member_ptr& operator-=(size_t offset)
        { off_ -= offset * sizeof(T); return *this; }
        member_ptr operator+(size_t offset) const
        { auto copy = *this; copy += offset; return copy; }
        member_ptr operator-(size_t offset) const
        { auto copy = *this; copy -= offset; return copy; }
    
        size_t offset() const { return off_; }
    };
    
    template<class M, typename T>
    member_ptr<M, T> make_member_ptr(T M::*a)
    { return member_ptr<M, T>(reinterpret_cast<uintptr_t>(&(((M*)nullptr)->*a)));}
    

    Now we can make the pointer to the array element directly:

    auto mp = make_member_ptr(&S::b)[2];
    S s;
    s->*mp = 123.4;
    
    // s.b[2] is now expectably 123.4
    

    Finally, if you really, really like materialized references, you may get a bit haskell-lensish and make them compose:

    // in class member_ptr, note transitivity of types M -> T -> TT:
        template<class TT>
        member_ptr<M,TT> operator+(const member_ptr<T,TT>&t)
        { return member_ptr<M,TT>(off_ + t.offset()); }
    
    // test:
    struct A { int a; };
    struct B { A arr[10]; };
    
    B x;
    auto p = make_member_ptr(&B::arr)[5] + make_member_ptr(&A::a)
    
    x->*p = 432.1;
    // x.arr[5].a is now expectably 432.1
    
    0 讨论(0)
  • 2021-02-04 06:42

    You can't do that out of the language itself. But you can with boost. Bind a functor to some element of that array and assign it to a boost::function:

    #include <boost/lambda/lambda.hpp>
    #include <boost/lambda/bind.hpp>
    #include <boost/function.hpp>
    #include <iostream>
    
    struct test {
        int array[3];
    };
    
    int main() {
        namespace lmb = boost::lambda;
    
        // create functor that returns test::array[1]
        boost::function<int&(test&)> f;
        f = lmb::bind(&test::array, lmb::_1)[1];
    
        test t = {{ 11, 22, 33 }};
        std::cout << f(t) << std::endl; // 22
    
        f(t) = 44;
        std::cout << t.array[1] << std::endl; // 44
    }
    
    0 讨论(0)
  • 2021-02-04 06:43

    The problem is that, accessing an item in an array is another level of indirection from accessing a plain int. If that array was a pointer instead you wouldn't expect to be able to access the int through a member pointer.

    struct foo
    {
      int a;
      int *b;
    };
    
    int main()
    {
    
      foo bar;
      int foo::* aptr=&(*foo::b); // You can't do this either!
      bar.a=1;
      std::cout << bar.*aptr << std::endl;
    }
    

    What you can do is define member functions that return the int you want:

    struct foo
    {
      int a;
      int *b;
      int c[2];
    
      int &GetA() { return a; } // changed to return references so you can modify the values
      int &Getb() { return *b; }
      template <int index>
      int &GetC() { return c[index]; }
    };
    typedef long &(Test::*IntAccessor)();
    
    void SetValue(foo &f, IntAccessor ptr, int newValue)
    {  
        cout << "Value before: " << f.*ptr();
        f.*ptr() = newValue;
        cout << "Value after: " << f.*ptr();
    }
    
    int main()
    {
      IntAccessor aptr=&foo::GetA;
      IntAccessor bptr=&foo::GetB;
      IntAccessor cptr=&foo::GetC<1>;
    
      int local;
      foo bar;
      bar.a=1;
      bar.b = &local;
      bar.c[1] = 2;
    
      SetValue(bar, aptr, 2);
      SetValue(bar, bptr, 3);
      SetValue(bar, cptr, 4);
      SetValue(bar, &foo::GetC<0>, 5);
    }
    

    Then you at least have a consistent interface to allow you to change different values for foo.

    0 讨论(0)
  • 2021-02-04 06:43

    However, the compiler just complains about an "invalid use of non-static data member 'foo::b'"

    This is because foo::a and foo::b have different types. More specifically, foo::b is an array of size 2 of ints. Your pointer declaration has to be compatible i.e:

    int (foo::*aptr)[2]=&foo::b;
    

    Is it possible to do this at all (or at least without unions)?

    Yes, see below:

    struct foo
    {
      int a;
      int b[2];
    };
    
    int main()
    {
    
      foo bar;
    
      int (foo::*aptr)[2]=&foo::b;
      /* this is a plain int pointer */
      int *bptr=&((bar.*aptr)[1]);
    
      bar.a=1; 
      bar.b[0] = 2;
      bar.b[1] = 11;
    
      std::cout << (bar.*aptr)[1] << std::endl;
      std::cout << *bptr << std::endl;
    }
    

    Updated post with OP's requirements.

    0 讨论(0)
  • 2021-02-04 06:57

    I'm not sure if this will work for you or not, but I wanted to do a similar thing and got around it by approaching the problem from another direction. In my class I had several objects that I wanted to be accessible via a named identifier or iterated over in a loop. Instead of creating member pointers to the objects somewhere in the array, I simply declared all of the objects individually and created a static array of member pointers to the objects.

    Like so:

    struct obj
    {
        int somestuff;
        double someotherstuff;
    };
    
    class foo
    {
      public:
        obj apples;
        obj bananas;
        obj oranges;
    
        static obj foo::* fruit[3];
    
        void bar();
    };
    
    obj foo::* foo::fruit[3] = { &foo::apples, &foo::bananas, &foo::oranges };
    
    
    void foo::bar()
    {
        apples.somestuff = 0;
        (this->*(fruit[0])).somestuff = 5;
        if( apples.somestuff != 5 )
        {
            // fail!
        }
        else
        {
            // success!
        }
    }
    
    
    
    int main()
    {
        foo blee;
        blee.bar();
        return 0;
    }
    

    It seems to work for me. I hope that helps.

    0 讨论(0)
  • 2021-02-04 06:58
      typedef int (foo::*b_member_ptr)[2];
      b_member_ptr c= &foo::b;
    

    all works.

    small trick for member and function pointers usage.
    try to write

    char c = &foo::b; // or any other function or member pointer
    

    and in compiller error you will see expected type, for your case int (foo::*)[2].

    EDIT
    I'm not sure that what you want is legal without this pointer. For add 1 offset to your pointer you should get pointer on array from your pointer on member array. But you can dereference member pointer without this.

    0 讨论(0)
提交回复
热议问题