"Ambiguous overload" doesn't look ambiguous

Hi,

I've run into a rather perplexing issue. I've managed to distill it down into the code here: http://goo.gl/TYqayI

I'm basically trying to overload a function based on whether the given value is a pointer-to-member, pointer-to-const-member, or a pointer-to-member-function (accepting one argument and returning void). According to the latest versions of MSVC and Clang, this is ambiguous! But according to the latest version of GCC this is fine. I'm gonna go with GCC on this one, because I can't see how this is ambiguous at all. A field is either const or not, and pointers to member functions are unrelated to pointers to members, so determining the correct overload should be a piece of cake, right?

Is this a bug or am I missing something?

Thanks
Last edited on
A const as a parameter can take a non-const you know...
That's why the non-const overload is there. Besides, currently it thinks all three functions are candidates, while really it should be just one (the one accepting a pointer-to-member-function).
It might be a bug in clang (or might be how it is actully supposed to work, i can't say right now) but GCC works fine: http://coliru.stacked-crooked.com/a/7b84a6b5ff2a94b7

Edit: so does ICC. Anyone want to try this on VS?
Last edited on
GCC cannot compile this:

1
2
3
4
5
template <class OwnerType, typename FieldType>
void Test(const FieldType OwnerType::*)
{
	cout << "Const Field" << endl;
}


It discards the function silently.

Clang can compile this function, but since the const has no effect it is considered to be the same as the non const version.

For Visual C++ it is ambigous too.
GCC cannot compile this
It can actually: http://coliru.stacked-crooked.com/a/37c48a1a8d840e93

The fact that substitution failure happens is expected and everything works as it expected by most.

Question if it is really how it should behave. I am not ready to answer that question.
Anyone want to try this on VS?

An online Visual C++, in case anyone is interested:

online visual c++ compiler
http://webcompiler.cloudapp.net
Compiler version: 19.00.23015.0(x86) Last updated: Jun. 19, 2015

And the site wcassella's Goo.gl URL shortener points at is:

Compiler Explorer
http://gcc.godbolt.org/

which has assorted versions of Clang and GCC, plus ICC 13.0.1

Andy

PS Mostly for my own future reference...

More online compilers. Unfortunately the Comeau Test Drive (EDG 4.3.3) is down, and LiveWorkspace.org has gone AWOL.

Online C++ compilers
https://isocpp.org/blog/2013/01/online-c-compilers

LiveWorkspace (Clang 3.2, GCC 4.6.3 - 4.7.2)
dead link: http://liveworkspace.org/
gcc.godbolt.org (Clang 3.0, GCC 4.5.3 - 4.8.0 prerelease, Intel ICC 13.0.1)
http://gcc.godbolt.org/
Rise4Fun (Microsoft VC++ 2012 and November 2012 CTP)
http://webcompiler.cloudapp.net/
Stacked-Crooked (GCC 4.7)
http://coliru.stacked-crooked.com/
ideone.com (GCC 4.3.4 and 4.5.1)
http://ideone.com/
Comeau Test Drive (EDG 4.3.3)
defunct: http://comeaucomputing.com/tryitout/
Note that "tryitout" is down until further notice, therefore the form will fail.

Last edited on
Is there a way to actually code this that compiles?

1
2
3
4
void Test(void (int) Example::*)
{
    cout << "Huh?\n";
}


The type of

void func(int) {}

is void (int) but I don't see how you you can declare a parameter of this type. Is there some "arcane" way to do so?

If there isn't a way to do so then I'm with GCC. They've decided that void (int) is relevant when searching for compatible parameter types?

Andy
Using some SFINAE, I've managed to modify the code so that the over loads a no longer ambiguous on any compiler, see here: http://goo.gl/zVcoKo

(I'd also like to point at that this proves that the "const" in the second overload, DOES in fact have an effect: it declares it as a "pointer-to-const").

While it works well, its rather frustrating that I have to resort to using that. It seems that some compilers think that "[signature] [owner name]::*" is a valid declaration for a pointer-to-member-function, but I've never seen that anywhere. I'm not sure what the standard says about it though.
The ambiguity is there because in:
1
2
template <class OwnerType, typename FieldType>
void Test(FieldType OwnerType::*) ;

FieldType may be the type of a member object of OwnerType
=> type FieldType OwnerType::* is 'pointer to member object of OwnerType'

FieldType may be the type of a member function of OwnerType
=> type FieldType OwnerType::* is 'pointer to member function of OwnerType'

The ambiguity can be resolved by using type_traits in conjunction with SFINAE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <type_traits>

template < typename T, typename MEMBER > // (1)
typename std::enable_if< std::is_member_object_pointer< MEMBER T::* >::value >::type
test( const MEMBER T::* )
{
    std::cout << "(1) pointer to const member object\n" ;
}

template < typename T, typename MEMBER > // 2
typename std::enable_if< std::is_member_object_pointer< MEMBER T::* >::value >::type
test( MEMBER T::* )
{
    std::cout << "(2) pointer to non-const member object\n" ;
    // note: (1) is a better match than (2) for 'pointer to const member object'
}

template < typename T, typename MEMBER > // (3)
typename std::enable_if< std::is_member_function_pointer<MEMBER T::*>::value >::type
test( MEMBER T::* )
{
    std::cout << "(3) pointer to member function\n" ;
}

int main()
{
    struct example
    {
        int non_const_member_object = 0 ;
        const int const_member_object = 2 ;
        void member_function(int) {}
    };

    test( &example::non_const_member_object ) ; // (2) pointer to non-const member object
    test( &example::const_member_object ) ; // (1) pointer to const member object
    test( &example::member_function ) ; // (3) pointer to member function
}

clang++ and g++: http://coliru.stacked-crooked.com/a/35356d10a19b49c2
Microsoft: http://rextester.com/TNOI36059

EDIT:
> Using some SFINAE, I've managed to modify the code so that the over loads a no longer ambiguous on any compiler,

Yes. Hadn't seen that post while I was making this post.

> [signature] [owner name]::*" is a valid declaration for a pointer-to-member-function

It is a valid declaration (all three mainstream compilers accept it):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
    struct example
    {
        int non_const_member_object = 0 ;
        const int const_member_object = 2 ;
        void member_function(int) {}
    };

    using fn_type = void(int) ;
    fn_type example::*pmfn = &example::member_function ;
    
    void  (example::*pmfn2)(int) = &example::member_function ;
    
    return pmfn == pmfn2 ? 0 : 1 ;
}

http://coliru.stacked-crooked.com/a/94482bb975a5fcda
http://rextester.com/GKT89154
Last edited on
Thanks for the response, I never knew you could do that! One thing I love about C++ is there's always more to learn, so you never get bored! (Though you might get frustrated)
Checked this with the standard:
If the type of a function is unqualified, it could be the type of either a member function or a non-member function.
If the type of a function is cv qualified or ref qualified, it can only be the type of a non-static member function.

clang++ appears to be the only mainstream compiler that is fully conforming with respect to this.
g++ (5.1) is broken for ref qualified function types
Microsoft does not support ref qualified member functions at all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void non_member_function(int) {}

int main()
{
    struct example
    {
        void unqualified_member_function(int) {}
        static void static_member_function(int) {}

        void const_qualified_member_function(int) const {}
        void ref_qualified_member_function(int) & {}
    };

    using member_or_non_member_fn_type = void(int) ;
    member_or_non_member_fn_type example::*pmfn = &example::unqualified_member_function ;
    member_or_non_member_fn_type* pfn = non_member_function ;
    pfn = &example::static_member_function ;

    using const_qualified_member_fn_type = void(int) const ;
    const_qualified_member_fn_type example::*pmfnc = &example::const_qualified_member_function ;

    using ref_qualified_member_fn_type = void(int) & ;
    ref_qualified_member_fn_type example::*pmfnr = &example::ref_qualified_member_function ;
}

http://coliru.stacked-crooked.com/a/c3aa7e0cfc900147
http://rextester.com/SYL31375
The latest version of MSVC actually does have ref-qualified member functions, 2015 RC. The previous versions of MSVC are crap anyway, seeing as classes don't get auto-generated move constructors, and half the type_traits stuff doesn't work.

It seems that they're still not quite fully supported though: the editor won't let me collapse functions that have ref-qualifiers.
Last edited on
Topic archived. No new replies allowed.