Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Pointer to a constant member variable

Credits go to anonymous, ImLeftFooted, and Oscar Giner for helping out!
This thread is locked; no one can reply to it. rss feed Print
Pointer to a constant member variable
relpatseht
Member #5,034
September 2004
avatar

Hello all,

I've just been plugging away at my binding library and I found the compiler finds ambiguity in the two following declarations:

// Support for binding a constant member variable 
// (makes a read-only variable on the JS side)
template<typename T, typename C>
JSObject& AddVar(const std::string& name, const T C::* var);

// Binding of a getter which is non-const for some reason
// Makes a read-only variable on the JS side
template<typename T, typename C>
JSObject& AddVar(const std::string& name, T (C::*Get)(void));

My first assumption is I don't actually know the syntax for a pointer to a constant member variable. The compiler has no problem differentiating between the two following:

// Binding of a getter which is non-const for some reason
// Makes a read-only variable on the JS side
template<typename T, typename C>
JSObject& AddVar(const std::string& name, T (C::*Get)(void));

// Binding of a getter
// Makes a read-only variable on the JS side
template<typename T, typename C>
JSObject& AddVar(const std::string& name, T (C::*Get)(void) const);

Which makes perfect sense considering the standard specified pointers to constant member functions and pointers to non-const member functions may not be the same. Having not read the standard, I took this and assumed the same would be true of const and non-const pointers to member variables, allowing me to templatize the two separately so I could simply not generate setters for the const member variable pointers. I suppose this also may have been an invalid assumption, but I cannot find my information on the subject.

What am I doing or assuming wrong?

kazzmir
Member #1,786
December 2001
avatar

Giving a name to things doesn't change their type. These two are ambiguous

void foo(int x);
void foo(int);

Adding const does make the type different from the non-const version.

void foo(const int x);
void foo(int x);

Is that all the information you wanted or did I misinterpret your question?

<edit>
Sorry I just noticed your first function has 'const' where the second doesn't. So.. I guess you're right about needing to put the const at the end.

relpatseht
Member #5,034
September 2004
avatar

Yes, I know, but I'm talking about pointers to member variables. I'm about positive const T C::* var doesn't make a pointer to a constant member variable, but I'm not sure what the syntax to do so is.

I know a pointer to a member function and a pointer to a constant member function are different types, so I'm assuming the same is true of member variables, but it may not. If you could shed some light on the situation, I'd be appreciative.

kazzmir
Member #1,786
December 2001
avatar

I don't think theres a way to declare a function that takes variables from a specific class. Above your functions accept parameters whose type is a function that returns T and is a member of C. You can't specify variables in the class C.

When const is applied to parameter values it only disambiguates in the case when you pass by reference.

Can you maybe show the code that uses the AddVar functions?

Oscar Giner
Member #2,207
April 2002
avatar

The 'Pointer to member variable' concetp doesn't exist. You just use a plain normal pointer to point to a member variable.

relpatseht
Member #5,034
September 2004
avatar

No, pointers to member variables definitely exist. They are very similar to pointers to member functions. They are rarely used (which I can understand, pointers to member functions are rarely used, and classes rarely expose member variables), but they are real and completely necessary for what I am doing.

#SelectExpand
1class Foo 2{ 3 public: 4 int v; 5 Foo() : v(0){} 6}; 7 8typedef int (Foo::*IntMemberPointer); 9 10int main() 11{ 12 Foo bar, fish; 13 IntMemberPointer q = &Foo::v; 14 15 bar.*q = 6; // bar.v = 6, fish.v = 0 16 fish.*q = 42; // bar.v = 6, fish.v = 42 17 return 0; 18}

Anyway, the standard specifies that a pointer to a non-const member function is not the same type as a pointer to a const member function, ie, the following two types are distinct.

typedef void (Foo::*Bar)(void);
typedef void (Foo::*CBar)(void) const;

Based on this, I am assuming the same is true of non-const member variables and const member variables, however, I don't know the syntax for a pointer to a const member variable, if it is indeed distinct from a pointer to a non-const member variable.

typedef int (Foo::*var);
typedef int (Foo::*cvar) const; // Not proper syntax

Oscar Giner
Member #2,207
April 2002
avatar

 IntMemberPointer q = &Foo::v;

You can't get the address of something that doesn't exist. Because Foo::v doesn't exist in memory. I think you're confusing this feature with other language that may have this feature, but C++ doesn't.

kazzmir
Member #1,786
December 2001
avatar

Apparently its a real c++ feature. Well I learned something new today.

Anyway, relpatseht you might want to ask on stackoverflow.com I guess.

anonymous
Member #8025
November 2006

I don't see anything wrong with the code. What compiler are you using and is it saying the overloads are ambiguous as such or only for a particular instantiation? Perhaps you could show all overloads?

kazzmir said:

Adding const does make the type different from the non-const version.

With pointers it matters what the const applies to. A signature with a "pointer to const object" is an overload of a function that takes just a pointer.

void foo(int*);
void foo(int* const); //same as previous
void foo(const int*); //overload

ImLeftFooted
Member #3,935
October 2003
avatar

<cut irrelevant stuff>

Doing this solves it, but I have no idea why.

#SelectExpand
1#include <iostream> 2using namespace std; 3 4class Foo { 5public: 6 Foo(int k) : k(k) {} 7 8 const int k; 9 int getk() const { return k; } 10}; 11 12template<typename T, typename C> 13void output(const T &t, C (T::*getter)() const) { 14 15 cout << (t.*getter)() << endl; 16} 17 18template<typename T, typename C> 19void output(T &t, const C T::* var) { 20 21 cout << t.*var << endl; 22} 23 24void main() { 25 26 Foo f(5); 27 28 output(f, &Foo::k); 29 output(f, &Foo::getk); 30}

Not implementing void output(T &t, C T::* var) removes the ambiguity.

Said explicitly, don't make a member variable pointer an overload unless it has a const.

anonymous
Member #8025
November 2006

In this case, part of the problem seems to be with the type of the first argument. If you pass a rvalue, then T& is a better match than a const T& whereas in the second argument C can be equally well deduced as int or int() const.

You might need to disable unsuitable overloads with enable_if/disable_if (also to be available in C++0x standard library).

#SelectExpand
1#include <iostream> 2#include <boost/type_traits.hpp> 3#include <boost/utility/enable_if.hpp> 4using namespace std; 5 6class Foo { 7public: 8 Foo(int k) : k(k) {} 9 10 int k; 11 int getk() const { return k; } 12}; 13 14template<typename T, typename C> 15void output(const T &t, C (T::*getter)() const) { 16 17 cout << (t.*getter)() << endl; 18} 19 20template<typename T, typename C> 21typename boost::disable_if<boost::is_member_function_pointer<C T::*>, void>::type output(const T &t, const C T::* var) { 22 23 cout << "Const pointer " << t.*var << endl; 24} 25 26template<typename T, typename C> 27typename boost::disable_if<boost::is_member_function_pointer<C T::*>, void>::type output( T &t, C T::* var) { 28 29 cout << "Non-const pointer " << t.*var << endl; 30} 31 32int main() { 33 34 Foo f(5); 35 36 output(f, &Foo::k); 37 output(f, &Foo::getk); 38}

In this case it is unclear, though, why the third overload would be needed. A const pointer to member/object doesn't mean that the member/object has to be a constant, it just restricts what you do with the pointer.

int i = 10;
const int* p = &i; //fine

Tobias Dammers
Member #2,604
August 2002
avatar

No, pointers to member variables definitely exist. They are very similar to pointers to member functions. They are rarely used (which I can understand, pointers to member functions are rarely used, and classes rarely expose member variables), but they are real and completely necessary for what I am doing.

They do exist, and they are used all the time, but they are exactly the same as pointers to free variables. If you have a variable of type int*, then you can point it to an integer member variable, to an integer local variable, you can allocate a new integer and point there, it doesn't matter. This is different from function pointers: a pointer to a free function (or static member function) is not compatible with a pointer to a (non-static) member function. In code:

#SelectExpand
1class Foobar { 2 int x; 3 Foobar(int _x) : x(_x) { } 4 void setX(int value) { x = value; } 5}; 6 7void freeSetter(int value) { /* need to do something with value here */ } 8 9typedef (*IntSetter)(int); 10 11Foobar foobar(23); 12int baz = 32; 13 14int* p; 15 16p = &(foobar.x); // this is valid 17p = &baz; // so is this 18 19IntSetter setter = freeSetter; // This is OK 20setter = foobar.setX; // this is not.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Oscar Giner
Member #2,207
April 2002
avatar

Tobias: indeed pointers to member variables do exist, and they're not standard pointers. Yes, you can make a standard pointer point to a member variable (unlike with function pointers), too, but pointers to member variables is a different concept. I hadn't heard of this C++ feature before (I don't think my C++ book did even mention them), and actually I still can't think of a situation where they may be useful. I guess it's one of those obscure C++ features a lot of people (including myself until now) don't know they exist, and very rarely used.

#SelectExpand
1class A 2{ 3public: 4 int a; 5 int b; 6}; 7 8main() 9{ 10 A obj1, obj2; 11 int A::*p; // pointer to a member of class A, of type int 12 13 p = &A::a; 14 obj1.*p = 5; // obj1.a = 5; 15 obj2.*p = 5; // obj2.a = 5; 16 17 p = &A::b; 18 obj1.*p = 10; // obj1.b = 10; 19 obj2.*p = 10; // obj2.b = 10; 20}

If I understood it correctly that's how it works :P

Tobias Dammers
Member #2,604
August 2002
avatar

And people wonder why C++ is considered "hard"... ::)

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

relpatseht
Member #5,034
September 2004
avatar

Ah alright. So it seems then a pointer to a constant member variable and a pointer to a member variable are not distinct types as with member functions. I will be looking into the enable_if and disable_if functionality, as that should solve my problem.

The reason I need member variable pointers, by the way, is for my JavaScript binding library. It makes much more sense (and is really the only plausible way) to store what an object looks like so copies can be made and accessed from script. So you just tell the library where the member variables are and where the member functions are then all the objects created can use that information to access them (considering the script has no idea what the object looks like, it just knows where it is in memory).

ImLeftFooted
Member #3,935
October 2003
avatar

So it seems then a pointer to a constant member variable and a pointer to a member variable are not distinct types as with member functions.

That's wrong. They are distinct types and can be overloaded.

The only issue is a the member variable pointers have higher priority than member function pointers. For some reason, you can subvert this priority by making the member variable pointer overload of const type.

As proof, this code runs fine:

#SelectExpand
1#include <iostream> 2using namespace std; 3 4class Foo { 5public: 6 Foo(int k) : k(k), v(k * 10) {} 7 8 const int k; 9 int v; 10}; 11 12template<typename T, typename C> 13void output(T &t, const C T::* var) { 14 15 cout << "const " << t.*var << endl; 16} 17 18template<typename T, typename C> 19void output(T &t, C T::* var) { 20 21 cout << t.*var << endl; 22} 23 24int main() { 25 26 Foo f(5); 27 28 output(f, &Foo::k); 29 output(f, &Foo::v); 30}

$ g++ test.cpp -o test && ./test
const 5
50

anonymous's explanation is spot on and worded clearly.

anonymous
Member #8025
November 2006

What is surprising to me is that the template argument looking like a pointer-to-member (T C::*) can also be used for passing pointer-to-member-function.

However, it seems that only GCC will be able to bind a constant member function to it, and other compilers (VC++, Comeau) only compile the following if CONST_CORRECT is not defined.

#SelectExpand
1#include <iostream> 2#define CONST_CORRECT 3#ifdef CONST_CORRECT 4#define CONSTANT const 5#else 6#define CONSTANT 7#endif 8 9struct X 10{ 11 void operator()(int n) CONSTANT { std::cout << "X("<< n << ")\n"; } 12}; 13 14struct Y 15{ 16 void foo(int n) CONSTANT { std::cout << "foo(" << n << ")\n"; } 17 X x; 18}; 19 20template <class T, class C> 21void bar(CONSTANT C& c, T C::*ptr) 22{ 23 (c.*ptr)(10); 24} 25 26int main() 27{ 28 Y y; 29 bar(y, &Y::x); 30 bar(y, &Y::foo); 31}

relpatseht
Member #5,034
September 2004
avatar

I see. Odd indeed. I should probably look up what the standard says in this case to see who's doing what wrong. Out of curiosity, what version of GCC were you using, anonymous? I haven't gone yet tried to compile this library on GCC, but I generally stick very close to standard and have been using templates exclusively on GCC for a long time, so I usually have no trouble porting between GCC and MSVC. Hopefully I won't have to use #ifdefs to get around this. The enable and disable if functionality will hopefully sort it all out.

bamccaig
Member #7,536
July 2006
avatar

/me still doesn't understand how Boost can implement disable_if and enable_if purely in code. :-X
/me also hasn't looked up the documentation, however (nor used either).

anonymous
Member #8025
November 2006

Out of curiosity, what version of GCC were you using, anonymous?

4.4.1.

bamccaig said:

/me still doesn't understand how Boost can implement disable_if and enable_if purely in code. :-X

These are really trivial to implement. enable_if_c (specialized on a bool, not on true_type/false_type) looks like this:

template <bool B, class T>
struct enable_if_c;

template <class T>
struct enable_if_c<true, T>
{
    typedef T type;
};

template <class T>
struct enable_if_c<false, T>
{};

That's it. How it works:

template <class T>
typename enable_if_c<Condition<T>::value, void>::type foo(T t);

If Condition<T>::value is false, this causes a Substitution Failure (this specialization does not typedef type), which is Not An Error (this function is simply not an overload candidate, compilation proceeds ignoring this function).

kazzmir
Member #1,786
December 2001
avatar

I used pointers to members to simplify some code today, so thanks a lot!

bamccaig
Member #7,536
July 2006
avatar

I was trying to use them to clean up rot on Thursday, but it wasn't compiling and I didn't have time to figure it out. :(

relpatseht
Member #5,034
September 2004
avatar

Well, I finally solved a major hangup and had time to come back to this, but there seems to be something wrong with my is_member_function.

#SelectExpand
1template<typename R, typename C> 2 3template<bool Cond, typename T = void> 4struct disable_if 5{ 6 typedef T type; 7}; 8 9template<typename T> 10struct disable_if<true, T> {}; 11 12struct is_member_function<R (C::*)()> 13{ 14 static const bool value = true; 15}; 16 17class Tuple2 18{ 19 public: 20 float LengthSqr(); // Works 21 Tuple2 Normalize(); // Doesn't work 22};

I'm a bit stumped on this one, honestly. &Tuple2::Normalize does not instantiate the above template as one would suspect when passed as a parameter to AddVar. Any ideas?

anonymous
Member #8025
November 2006

The code is really messed up. Probably the problem is that you are trying to instantiate a is_member_function<&Tuple2::Normalize> instead of is_member_function<Tuple2 Tuple2::*()> (passing a value instead of a type, just like trying to instantiate a std::vector<42> instead of std::vector<int>).

However, type_traits is not something I'd like to implement myself.

relpatseht
Member #5,034
September 2004
avatar

No, I'm not instantiating &Tuple2::Normlize, I'm passing that to my AddVar function, I probably should have emphasized that.

I'm instantiating T C::*, which, for &Tuple2::Normalize, would be is_member_function<Tuple2 Tuple2::*>. I'm not sure why that comes out as a member function when is_member_function<float Tuple2::*> doesn't.

Go to: