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
?
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With