Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Member pointer to array element

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::* aptr=&foo::a; bar.a=1; std::cout << bar.*aptr << std::endl; }

Now I need to have a pointer to a specific element of an array, so normally I'd write
int foo::* bptr=&(foo::b[0]);
However, the compiler just complains about an "invalid use of non-static data member 'foo::b'" Is it possible to do this at all (or at least without unions)?

Edit: I need a pointer to a specific element of an array, so int foo::* ptr points to the second element of the array (foo::b[1]).

Yet another edit: I need to access the element in the array by bar.*ptr=2, as the pointer gets used somewhere else, so it can't be called with bar.*ptr[1]=2 or *ptr=2.

like image 775
tstenner Avatar asked Mar 23 '09 18:03

tstenner


People also ask

How do I turn a pointer point into an array of elements?

int *ptr = A; // ptr now also points to the start of the array. This pointer points to the first element in the array. You can dereference that pointer to access the array element. Pointer variables that point into an array can also be moved around in the array via the pointer increment and decrement operations.

Can we assign a pointer to array?

It is legal to use array names as constant pointers, and vice versa. Therefore, *(balance + 4) is a legitimate way of accessing the data at balance[4]. In the above example, p is a pointer to double, which means it can store the address of a variable of double type.

What is pointer to array in C++?

In C++, Pointers are variables that hold addresses of other variables. Not only can a pointer store the address of a single variable, it can also store the address of cells of an array.

What is pointer to array explain with example?

CProgrammingServer Side Programming. Pointers are variables which stores the address of another variable. When we allocate memory to a variable, pointer points to the address of the variable. Unary operator ( * ) is used to declare a variable and it returns the address of the allocated memory.


6 Answers

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.

like image 162
dirkgently Avatar answered Oct 12 '22 05:10

dirkgently


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.

like image 25
Eclipse Avatar answered Oct 12 '22 04:10

Eclipse


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
like image 20
exa Avatar answered Oct 12 '22 06:10

exa


  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.

like image 27
bayda Avatar answered Oct 12 '22 04:10

bayda


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
}
like image 28
Johannes Schaub - litb Avatar answered Oct 12 '22 04:10

Johannes Schaub - litb


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.

like image 28
Jonathan Swinney Avatar answered Oct 12 '22 05:10

Jonathan Swinney