Bug 93740 - Template base classes parametrized by pointer-to-member are amibiguous
Summary: Template base classes parametrized by pointer-to-member are amibiguous
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.2.1
: P3 normal
Target Milestone: 14.0
Assignee: Not yet assigned to anyone
URL:
Keywords: rejects-valid
Depends on:
Blocks: constexpr pmf, ptmf
  Show dependency treegraph
 
Reported: 2020-02-14 11:47 UTC by m.cencora
Modified: 2024-05-28 12:52 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-07-23 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description m.cencora 2020-02-14 11:47:04 UTC
Following code does not compile on any gcc version regardless of chosen C++ standard version, but compiles correctly on clang, msvc and icc.

struct A
{
    virtual void foo() = 0;
    virtual void bar() = 0;
};

template <typename T, T val>
struct const_val{};

template <int N, typename T>
struct indexed_elem{};

using mem_fun_A_foo = const_val<decltype(&A::foo), &A::foo>;
using mem_fun_A_bar = const_val<decltype(&A::bar), &A::bar>;

struct A_indexed_member_funcs
  : indexed_elem<0, mem_fun_A_foo>,
    indexed_elem<1, mem_fun_A_bar>
{};

template <typename T, int N>
constexpr int index_of(indexed_elem<N, T>)
{
    return N;
}

void test()
{
    static_assert(index_of<mem_fun_A_foo>(A_indexed_member_funcs{}) == 0, "");
}
Comment 1 m.cencora 2020-03-03 14:40:11 UTC
Ping
Comment 2 Marek Polacek 2020-03-03 14:48:20 UTC
Confirmed, presumable we (gcc) are wrong here.
Comment 3 m.cencora 2020-12-03 10:51:29 UTC
Still broken in gcc 10.2 and trunk, also in C++20 mode.

The problem seems to show up only when address of member function (virtual or not) is passed as template parameter - only then gcc fails during overload resolution.

For free functions this works fine. 

struct A
{
    void foo();
    void bar();
};

template <int N, auto val>
struct indexed_elem{};

struct A_indexed_member_funcs
  : indexed_elem<0, &A::foo>,
    indexed_elem<1, &A::bar>
{};

template <auto val, int N>
constexpr int index_of(indexed_elem<N, val>)
{
    return N;
}

void foo();
void bar();

struct indexed_free_funcs
  : indexed_elem<0, &foo>,
    indexed_elem<1, &bar>
{};

void test()
{
    // this fails due to gcc claim of ambiguous base classes
    static_assert(index_of<&A::foo>(A_indexed_member_funcs{}) == 0, "");

    // this is ok
    static_assert(index_of<&foo>(indexed_free_funcs{}) == 0, "");
}
Comment 4 m.cencora 2020-12-03 11:07:26 UTC
I have done some more experiments, and it seems the problem applies to all pointer-to-member (not just pointer to member function, but also for pointer to data member).

Also it doesn't matter if these pointer-to-members are all from same class or not, error is the same.
Comment 5 m.cencora 2022-01-25 18:23:10 UTC
I think I was able to narrow it down to the true root cause. Following fails in all gcc versions that supports C++11 and newer:

struct foo
{
    void baz();
    void bar();
};

static_assert(&foo::baz != &foo::bar, "");

You may ask why I think so, because when we compile following code with -std=c++17 in gcc6.x, gcc7.x or gcc8.x:
struct foo
{
    void baz();
    void bar();
};

constexpr auto foo_baz()
{
    return &foo::baz;
}

constexpr auto foo_bar()
{
    return &foo::bar;
}

template <void (foo::*)()>
struct member_fun_ptr;

using member_foo_baz = member_fun_ptr<foo_baz()>;
using member_foo_bar = member_fun_ptr<foo_bar()>;

we get following errors:
<source>:20:48: error: 'void (foo::*)(){foo::baz, 0}' is not a valid template argument for type 'void (foo::*)()'
 using member_foo_baz = member_fun_ptr<foo_baz()>;
                                                ^
<source>:20:48: note: it must be a pointer-to-member of the form '&X::Y'
<source>:21:48: error: 'void (foo::*)(){foo::bar, 0}' is not a valid template argument for type 'void (foo::*)()'
 using member_foo_bar = member_fun_ptr<foo_bar()>;

which indicates that both &foo::baz and &foo::bar both have value '0' in compile time, which is certainly not correct (and that is most likely why we get "ambiguous base class" error in the example from comment#1).
Comment 6 GCC Commits 2023-12-13 20:57:23 UTC
The master branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:c822ad86984e752734b9c371f9cfef9330334ec4

commit r14-6519-gc822ad86984e752734b9c371f9cfef9330334ec4
Author: Patrick Palka <ppalka@redhat.com>
Date:   Wed Dec 13 15:55:01 2023 -0500

    c++: unifying FUNCTION_DECLs [PR93740]
    
    unify currently always returns success when unifying two FUNCTION_DECLs
    (due to the is_overloaded_fn deferment within the default case), which
    means for the below testcase we incorrectly unify &A::foo and &A::bar
    leading to deduction failure for the index_of calls due to a bogus base
    class ambiguity.
    
    This patch makes unify handle FUNCTION_DECL naturally like other decls.
    
            PR c++/93740
    
    gcc/cp/ChangeLog:
    
            * pt.cc (unify) <case FUNCTION_DECL>: Handle it like FIELD_DECL
            and TEMPLATE_DECL.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/template/ptrmem34.C: New test.
Comment 7 Patrick Palka 2023-12-13 20:58:25 UTC
Fixed for GCC 14.
Comment 8 Saurav Yadav 2024-05-28 12:52:20 UTC
It seems like bug 104678 might be related to this