4

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

Bernd
  • 2,113
  • 8
  • 22
  • Have you thought about using friend free functions in place of member functions? – Oliv Jul 05 '20 at 18:38
  • if you drop the `using A::Do;` in the second update, `A::Do` is hidden by `B:Do`. But I guess your real situation is more complex, where `A::Do` is in fact a variadic template and `B::Do` a different variadic template. I think you need to give a more realistic example. – JHBonarius Jul 09 '20 at 09:34
  • Yes, this using is not "C++ using". I added therefore the comment to explain what I want. In C++ all inherited methods become hidden by introducing a new function with the same name in the child class - this is called "hide by name". My aim is to hide only inherited functions which are already callable with child methods. This is called "hide by signature". The behaviour should be like in CSharp not like usual C++. In CSharp it is a bit more complex - therefore ignore visibility modifiers. – Bernd Jul 09 '20 at 10:29

1 Answers1

4

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...);
    }
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you Barry for your answer. You implementation results more or less in a "dispatcher" function. But what I want is a way to make a variadic function overload worse. I will edit my question. – Bernd Jul 05 '20 at 20:09
  • 1
    @BerndBaumanns I have no idea what that means, nor do I understand your edit (your `Do` with `...` isn't a worse candidate because of the `...`, it's simply not a candidate at all). You can't just make one function worse unless `A` knows in advance that `B` exists... and then is `B` the last one, or is there a `C` too? – Barry Jul 05 '20 at 22:27
  • Sorry, you are right - it was a mistake. I changed it again. I want to define a macro that defines a function which is only selected if there is no better match. Yes, there can be C and even D... – Bernd Jul 05 '20 at 22:54
  • And A should know nothing about B. B should not be changed just because A added a method with a name already defined in B. – Bernd Jul 05 '20 at 23:14
  • @BerndBaumanns "which is only selected if there is no better match" - But this is the opposite of what you asked, you had wanted `B::Do()` to be called even if `A::Do()` is the better match. – Barry Jul 05 '20 at 23:21
  • Yes, inherited methods should be last. Direct members should always be preferred. – Bernd Jul 06 '20 at 05:34
  • This should be even the case if the direct member would require an implicit conversion and the inherited method not. – Bernd Jul 06 '20 at 06:37
  • So we can't use using - using would make them available at the same level – Bernd Jul 06 '20 at 07:01