c++overloadingvariadic-functionsc++20name-hiding

Variadic function Overloading and SFINAE - Solve ambiguity to simulate "hide by signature"


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?


Update

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;
}

Update

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...


Solution

  • 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...);
        }
    };