VC++ bug? member pointers, and member function pointers

Started by
12 comments, last by godecho 15 years, 9 months ago
The following doesn't compile in VC++ 8:

struct Foo
{
    int a, b;
    int getB() const { return b; }
};

template <class T> struct Bar
{
    template <class M, M  T::* m>          void add() { /* do something with member_ptr */ }
    template <class M, M (T::* m)() const> void add() { /* do something with member_function_ptr */ }
};

int main(int argc, char** argv)
{
    Bar<Foo> bar;
    bar.add<int, &Foo::a>();
    bar.add<int, &Foo::getB>();
    return 1;
}


This works in GCC and Borland. Is it legal C++? Is there some trick to get this to work in VC++? The background and motivation for this construct comes from this recent thread.
Advertisement
this is the error that the code from the previous code produces:


error C2440: 'specialization' : cannot convert from 'int Foo::* ' to 'int (__thiscall Foo::* const )(void) const' There is no context in which this conversion is possible
error C2973: 'Bar<T>::add' : invalid template argument 'int Foo::* ' with [ T=Foo ] see declaration of 'Bar<T>::add' with [ T=Foo ]
error C2668: 'Bar<T>::add' : ambiguous call to overloaded function with [ T=Foo ]
could be 'void Bar<T>::add<int,pointer-to-member(0x0)>(void)' with [ T=Foo ]
or 'void Bar<T>::add<int,pointer-to-member(0x0)>(void)' with [ T=Foo ]
while trying to match the argument list '(void)'

error C2440: 'specialization' : cannot convert from 'int (__thiscall Foo::* )(void) const' to 'int Foo::* const ' There is no context in which this conversion is possible
error C2973: 'Bar<T>::add' : invalid template argument 'int (__thiscall Foo::* )(void) const' with [ T=Foo ] see declaration of 'Bar<T>::add' with [ T=Foo ]
error C2668: 'Bar<T>::add' : ambiguous call to overloaded function with [ T=Foo ]
could be 'void Bar<T>::add<int,int Foo::getB(void) const>(void)' with [ T=Foo ]
or 'void Bar<T>::add<int,int Foo::getB(void) const>(void)' with [ T=Foo ]
while trying to match the argument list '(void)'
Section 14.3.2 discusses template non-type parameters; you may consider it worthwhile reading. In particular, the first paragraph in that section calls out that a non-type parameter must be one of a variety of things, including
Quote:
the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference;


The third paragraph goes on to state that addresses of array elements and names and non-static class members are illegal, and provides the following examples:
Quote:
template<int* p> class X { };
int a[10];
struct S { int m; static int s; } s;
X<&a[2]> x3; // error: address of array element
X<&s.m> x4; // error: address of non-static member
X<&s.s> x5; // error: &S::s must be used
X<&S::s> x6; // OK: address of static member


So, short version is it looks like it's not legal, from my interpretation.
Thanks jpetrie. I guess that answers that question.
Quote:Original post by jpetrie
So, short version is it looks like it's not legal, from my interpretation.


That would make the implementation of std::tr1::is_member_function_pointer<> and std::tr1::is_member_object_pointer<> a bit difficult, isn't it? That would also make difficult the implementation of a template-based property<> object (where you need to specify either a getter or a setter).

I think the problem lies in the differences between M T::* m and M (T::* m)() const. I bet that VC++ has some difficulties to get the job done. Now, this can be either a bug in VC++ or a limitation of the standard. Unfortunately, I don't have much time to dig that right now, so I can't come with a better answer...

The fact is that this
struct Foo{    int a, b;    int getB() const { return b; }};template <class T> struct Bar{    // template <class M, M  T::* m> void add() { }    template <class M, M (T::* m)() const> void add() { }};int main(int argc, char** argv){    Bar<Foo> bar;    // bar.add<int, &Foo::a>();    bar.add<int, &Foo::getB>();    return 1;}

And this
struct Foo{    int a, b;    int getB() const { return b; }};template <class T> struct Bar{    template <class M, M  T::* m> void add() { }    // template <class M, M (T::* m)() const> void add() { }};int main(int argc, char** argv){    Bar<Foo> bar;    bar.add<int, &Foo::a>();    // bar.add<int, &Foo::getB>();    return 1;}

are correctly compiled on VC++, but this
struct Foo{    int a, b;    int getB() const { return b; }};template <class T> struct Bar{    template <class M, M (T::* m)() const> void add() { }    template <class M, M  T::* m> void add() { }};int main(int argc, char** argv){    Bar<Foo> bar;    // bar.add<int, &Foo::a>();    bar.add<int, &Foo::getB>();    return 1;}

doesn't compile.
Quote:
That would make the implementation of std::tr1::is_member_function_pointer<> and std::tr1::is_member_object_pointer<> a bit difficult, isn't it? That would also make difficult the implementation of a template-based property<> object (where you need to specify either a getter or a setter).

A fair point. I'm not that familiar with the ins and outs of the template parameter rules as set forth in the standard. The sections I quoted seem to disallow pointers-to-non-static-member functions pretty explicitly, although if there's one thing the standard is bad at, it's centralizing the location of rules based on contextual semantics.

It's possible there is an ambiguity in the resolution of the parameter similar to how the language prefers the type-id to the non-type value in some cases. There's a whole slew of special-case ambiguity and resolution rules in section 14.

Quote:
The fact is that this...

I'm assuming you compiled this with extensions disabled, right? As a matter of course I am extremely wary of whether or not code passes the compiler being used as supporting evidence one way or another, it's pretty easy to run afoul of undefined behavior or extensions, et cetera.
Quote:Original post by jpetrie
Quote:
The fact is that this...

I'm assuming you compiled this with extensions disabled, right? As a matter of course I am extremely wary of whether or not code passes the compiler being used as supporting evidence one way or another, it's pretty easy to run afoul of undefined behavior or extensions, et cetera.

Hum. No.

Point taken.

Although I'm - as you are - very aware of VC++ limitations and oddities when it comes to templates. MS seems to believe that "rules exist to be bended". Which I don't agree that much :)

I think 14.3.2 refers mainly to the impossible conversion between a non-static class member and a pointer. In the exemple, the standard uses int* p as the template parameter - and this cannot be matched by a non-static class member - hence 14.3.2.
A quick suggestion that may help solve the mystery, have you tried compiling it in G++ with extensions disabled?
Quote:Original post by stonemetal
A quick suggestion that may help solve the mystery, have you tried compiling it in G++ with extensions disabled?


I recompiled with __STRICT_ANSI__ defined, and it didn't complain. Is that the switch you were talking about?
Quote:Original post by godecho
Quote:Original post by stonemetal
A quick suggestion that may help solve the mystery, have you tried compiling it in G++ with extensions disabled?


I recompiled with __STRICT_ANSI__ defined, and it didn't complain. Is that the switch you were talking about?


I was thinking -pedantic -std=c++98 .
I think that is more or less what __STRICT_ANSI__ does but I am not sure.

This topic is closed to new replies.

Advertisement