Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does a SFINAEd-out function shadows an explicitly imported overload from the base class

After having hitten problems with another design, I decided make make a wrapper class to add overloads to a some member functions of the base class if and only if viable overloads do not already exist in the base class. Basically, here is what I am trying to do:

template<typename T>
struct wrapper: T
{
    using T::foo;

    template<typename Arg>
    auto foo(Arg) const
        -> std::enable_if_t<not std::is_constructible<Arg>::value, bool>
    {
        return false;
    }
};

struct bar
{
    template<typename Arg>
    auto foo(Arg) const
        -> bool
    {
        return true;
    }
};

In this simple example, wrapper adds an overloaded foo only if the one from the base class is not viable (I simplified the std::enable_if to the simplest possible thing; the original one involved the detection idiom). However, g++ and clang++ disagree. Take the following main:

int main()
{
    assert(wrapper<bar>{}.foo(0));
}

g++ is ok with it: the foo from wrapper<bar> is SFINAEd out so it uses the one from bar instead. On the other hand, clang++ seems to assume that wrapper<bar>::foo always shadows bar::foo, even when SFINAEd out. Here is the error message:

main.cpp:30:26: error: no matching member function for call to 'foo'
    assert(wrapper<bar>{}.foo(0));
       ~~~~~~~~~~~~~~~^~~

/usr/include/assert.h:92:5: note: expanded from macro 'assert'
  ((expr)                                                               \
        ^

/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with Arg = int]
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;
                                               ^

1 error generated.

So, who is right? Should this code be rejected just like clang++ does, or should it work and call bar::foo?

like image 709
Morwenn Avatar asked Nov 13 '15 13:11

Morwenn


3 Answers

Consider §10.2:

In the declaration set, using-declarations are replaced by the set of designated members that are not hidden or overridden by members of the derived class (7.3.3),

And §7.3.3 goes

When a using-declaration brings names from a base class into a derived class scope, […] member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5 [dcl.fct]), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).

Clearly, the only difference in your example lies within the return types. Thus Clang is correct and GCC is bugged.

The wording was introduced by CWG #1764:

According to 7.3.3 [namespace.udecl] paragraph 15,

When a using-declaration brings names from a base class into a derived class scope, […]

The algorithm for class-scope name lookup given in 10.2 [class.member.lookup], however, does not implement this requirement; there is nothing that removes a hidden base class member (replacing the using-declaration, per paragraph 3) from the result set.

The resolution was moved to DR in February 2014, so perhaps GCC didn't implement it yet.


As mentioned in @TartanLlama's answer, you can introduce a counterpart to handle the other case. Something along the lines of

template <typename Arg, typename=std::enable_if_t<std::is_constructible<Arg>{}>>
decltype(auto) foo(Arg a) const
{
    return T::foo(a);
}

Demo.

like image 179
Columbo Avatar answered Nov 19 '22 15:11

Columbo


This seems to be a bug; with the explicit using declaration, Clang has to consider the base overload. There's no reason for it not to.

Your code isn't correct, though, because there's no reason to prefer the restricted version to the base version when both are valid, so I believe you should get an ambiguity error if you pass something that isn't default-constructible.

like image 24
Sebastian Redl Avatar answered Nov 19 '22 15:11

Sebastian Redl


As @SebastianRedl says, this looks like a clang bug (Edit: looks like we were wrong).

Regardless of which compiler is correct, a possible workaround would be to define two versions of wrapper<T>::foo: one for when T::foo does not exist, which provides a custom implementation; and one for when it does, which forwards the call to the base version:

template<typename T>
struct wrapper: T
{
    template<typename Arg, typename U=T>
    auto foo(Arg) const
        -> std::enable_if_t<!has_foo<U>::value, bool>
    {
        return false;
    }

    template<typename Arg, typename U=T>
    auto foo(Arg a) const
        -> std::enable_if_t<has_foo<U>::value, bool>
    {
        return T::foo(a); //probably want to perfect forward a
    }
};

Live Demo

like image 1
TartanLlama Avatar answered Nov 19 '22 15:11

TartanLlama