I'd like to have "hiding by signature" instead of "hiding by name" in c++. So I wrote a macro which defines a variadic function that delegates all calls to it's base class if some exists. I can't use a using declaration because I don't want it to fail if the base class has no method with that name - and inherited methods should be considered only if no direct member matches. And this works most of the time because it is implemented by a variadic function which are always worse candidates compared to non variadic functions. But I have a problem when the child class has a variadic function, too -> the call becomes ambiguous.
So I get the following situation (simplified - without sfinae, macro...):
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX,
typename SomeSFINAE = int
>
void Do(TX...){
std::cout << "B::Do()\n";
}
template<typename... T>
void Do(T...){
A::Do();
}
};
int main(){
B b;
b.Do();
return 0;
}
See it on godbolt.
I'd like to solve this situation without making one of the method a "dispatcher-method". Is there a way to make one method a "worse candidate" to solve this ambiguity?
It seems not to be clear what I really want to achieve. So here some "pseudo-code" with comments:
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX
>
void Do(TX...){
std::cout << "B::Do()\n";
}
using A::Do; //<--- This should be considered only if no direct match is found in B
//Variadic function should win, because it is defined in B not in A - it should hide A.Do
//It should even work if A has NO method Do
};
int main(){
B b{};
b.Do(); //-> B::Do should be called, not A::Do
return 0;
}
What I want from you is something similar how you can make a normal function a worse candidate just for variadic functions.
For example:
#include <iostream>
void Do(int a){
std::cout << "better";
}
template<typename... T>
void Do(int a, T...){
//this is worse
std::cout << "worse";
}
int main(){
Do(42);
return 0;
}
Is there something which can make variadic function even worse?
Background: Currently I have the following macro, just to emulate a using like I want it.
#define NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, ...) \
private: template<typename $T, typename... $Args> \
using CallHiding$ ## AMETHOD = decltype(::std::declval<$T*>()->AMETHOD (::std::declval<$Args>()...)); \
\
public: template< \
typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, bool $Detected = ::CORE_NATIVE_NS ::is_detected_v<CallHiding$ ## AMETHOD, $Dependent, $Args...> \
, typename = typename ::std::enable_if_t<$Detected > \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
/*allow virtual call*/ \
return static_cast<$Dependent*>(this) -> AMETHOD (::std::forward<$Args>(args)...); \
} \
\
private: template<typename $T, typename $FktArgsTuple, typename $ValueArgsTuple> \
class CallHidingGeneric$ ## AMETHOD : public ::std::bool_constant<false> {\
};\
\
private: template<typename $T, typename... $FktArgs, typename... $ValueArgs> \
class CallHidingGeneric$ ## AMETHOD<$T, ::std::tuple<$FktArgs...>, ::std::tuple<$ValueArgs...>> \
{\
template<typename AType> \
using ATemplate = decltype(::std::declval<AType>().template AMETHOD <$FktArgs...> (::std::declval<$ValueArgs>()...)); \
public: \
constexpr static bool value = ::CORE_NATIVE_NS ::is_detected_v<ATemplate, $T> ; \
}; \
\
public: template< \
typename... $FktArgs \
, typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, typename = ::std::enable_if_t<(sizeof...($FktArgs) > 0)> \
, typename = ::std::enable_if_t< \
CallHidingGeneric$ ## AMETHOD<$Dependent, typename ::std::template tuple<$FktArgs...>, typename ::std::template tuple<$Args...>>::value \
> \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
return $Dependent ::template AMETHOD <$FktArgs...> (::std::forward<$Args>(args)...); \
}
#define NATIVE_DO_NOT_HIDE_INHERITED(AMETHOD) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, $Next)
#define NATIVE_DO_NOT_HIDE_INHERITED2(AMETHOD, ...) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, typename ::CORE_NATIVE_NS::type_container_t< __VA_ARGS__ >:: $Next)
It works fine with "normal" functions - but the "macro generated functions" are not considered worse...
Since you tagged this C++20, you can use a requires-clause to constrain B::Do
on either B::Do
or A::Do
being invokable, and then use if constexpr
in the body:
class B : public A
{
public:
template <typename... TX>
void Do(TX... ts)
requires true || requires (A a) { a.Do(ts...); }
{
if constexpr (true) {
std::cout << "B::Do()\n";
} else {
A::Do(ts...);
}
}
};
Here I'm using true
in place of the condition for B::Do
being invokable, so just replace that condition in both places as appropriate.
You could reduce the duplication by having the actual B::Do
implementation hidden in some other function:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
public:
template <typename... TX>
void Do(TX... ts)
requires requires (B b) { b.DoImpl(ts...); }
|| requires (A a) { a.Do(ts...); }
{
if constexpr (requires (B b) { b.DoImpl(ts...); }) {
B::DoImpl(ts...);
} else {
A::Do(ts...);
}
}
};
And now you just need to constrain B::DoImpl
A different approach still would be to use something like Boost.Hof's first_of()
adaptor (since that's what you're trying to do - invoke the first of a series of functions). This is a little awkward with member functions, but you can make it work with a private static member:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
static constexpr auto do_impl =
boost::hof::first_of(
[](B& b, auto... args) BOOST_HOF_RETURNS(b.DoImpl(args...)),
[](A& a, auto... args) BOOST_HOF_RETURNS(a.Do(args...)));
public:
template <typename... TX>
void Do(TX... ts)
requires requires { do_impl(*this, ts...); }
{
return do_impl(*this, ts...);
}
};