16

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.

1
  • @tstenner: It is possible.See my updated code. Commented Mar 23, 2009 at 19:10

7 Answers 7

8

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Ideally, I'd want a data pointer to a specific element, which should theoretically work, as the declaration of foo::b[1] is int.
@erikkallen: Great criticism. So, what value did you add to the entire discussion?
i think he is answering the question. whether at the user side one does (bar.*aptr)[1] or (bar.*aptr)[x] .. why does it matter. one can pass x along.
The difference the OP cares about is not (bar.*aptr)[1] vs (bar.*aptr)[x]; it's the difference between those and being able to do bar.*aptr, without any array accessors)
This seems to be the right track. I am thinking the complete answer will use "template arguments"
|
5

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.

7 Comments

Right, I just thought it should be possible since the actual array elements might/should have a fixed offset from the beginning of the object. Thanks for the clarification.
@Josh: Why are you suggesting it is not possible?
Because he's wanting an int foo::* that he can use to refer to the second element in an array in foo. You can have an int * that points to the second int in a specific foo instance, but you can't make plain int foo::* that refers to the int he wants.
The accessor function can be implemented using a template (template<int index> int& GetC(){return c[index];}), so it's quite easy to generate a function that returns a specefic element.
@underscore_d well, it seems that during my internal testing I've made some mistake. so Yes, my previous comment is invalid. thanks.
|
3

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

Comments

1
  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.

2 Comments

He doesn't want a pointer to the array but to a specific element.
@erikkallen In the absence of the language's ability to achieve that, the approximations delivered in this and dirkgently's answers are still highly useful for - the apparently very few of - us who want to understand what can and can't be done with this esoteric facet of the language. Sorry that you don't like it when a thread that could have just ended with 'No, it can't be done' instead says that and then presents a lot of other good ideas! +1 for the succinct example 'array of pointer-to-member' type and giving me the idea to fool the compiler into telling me what crazy type I need.
1

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
}

Comments

1

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.

3 Comments

Well, this won't work as soon as you have a second thread accessing the same code
Why not? This describes a compile-time technique for creating an array accessor for individually defined members. I see no reason this would be a problem with multithreaded code.
Wait, I see it works fine. You are using a static member, but that is only for the member pointers, which will be the same for all instances of the class. Carry on.
1

To re-state the goal here, we're looking for a type which can refer not only to a member of an object, but also to members of arrays within that object, provided that the members and array elements all have the same type. We need this reference type to be the same in both cases so that references to either can be used interchangeably.

So here's what I'm doing:

template <typename T>
struct getter_impl;

template <typename M, typename T>
struct getter_impl<M T::*> {
    template <M T::* ptr>
    static constexpr M& get(T* base) { return base->*ptr; }
};

template <typename M, typename T, size_t N>
struct getter_impl<std::array<M, N> T::*> {
    template <std::array<M, N> T::* ptr, size_t i>
    static constexpr M& get(T* base) { return (base->*ptr)[i]; }
};

template <auto ptr>
constexpr auto make_getter() { return getter_impl<decltype(ptr)>::template get<ptr>; }
template <auto ptr, size_t i>
constexpr auto make_getter() { return getter_impl<decltype(ptr)>::template get<ptr, i>; }

(more overloads may be added according to your needs)

This gives you the function make_getter(), and that function returns a pointer to another function which accepts a pointer to a class, and returns a reference to the given member within that class.

This provides int&(*)(foo*) in place of int foo::*, which is to say that rather than storing an offset to the member and calculating the address by adding that to the class pointer, it instead stores a pointer to a wafer-thin function which takes a class pointer and returns a reference to a member within that class (this function is simply one add operation and a return).

Not super efficient, but that seems to be where you end up every time C++ just stops dead before making a feature complete and broadly useful.

You can use it like so:

struct Object {
    uint32_t x, y;
    std::array<uint32_t, 4> v;
};

auto getx = make_getter<&Object::x>;
auto getv1 = make_getter<&Object::v, 1>;

uint32_t f(Object& obj, bool c) {
    auto get = getx;
    if (c) get = getv1;
    return get(&obj);  // get x or v1 from obj
}

Here's a trivial little "look up by name and get a pointer" example:

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

    uint32_t* find(std::string s) {
        auto i = by_name.find(s);
        if (i == by_name.end()) return nullptr;

        return &i->second(this);
    }

    static const std::map<std::string, int&(*)(foo*)> by_name;
};

const std::map<std::string, int&(*)(foo*)> foo::by_name = {
   {"a", make_getter<&foo::x>()},
   {"b0", make_getter<&foo::b, 0>()},
   {"v0", make_getter<&foo::b, 1>()}
};

Now... to properly generalise this make_getter<>() we would also need to add overloads to access C-style arrays, and also members of sub-objects within the object, and even members of elements of arrays of objects within the object, etc., and these overloads would be wrapped up in a way that expands recursively until it can produce the same trivial function as with simpler cases.

I believe that's possible, but I am not smart enough to do it.

1 Comment

some experimentation: godbolt.org/z/x8jWaT1Kr

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.