4

我想在 C++ 中使用“按签名隐藏”而不是“按名称隐藏”。所以我写了一个宏,它定义了一个可变参数函数,如果存在的话,它将所有调用委托给它的基类。我不能使用 using 声明,因为如果基类没有具有该名称的方法,我不希望它失败 - 并且只有在没有直接成员匹配时才应考虑继承的方法。这在大多数情况下都有效,因为它是由可变参数函数实现的,与非可变参数函数相比,它总是更差的候选者。但是当子类也具有可变参数函数时,我遇到了问题->调用变得模棱两可。

所以我得到以下情况(简化 - 没有sfinae,宏......):

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

在godbolt上看到它。

我想在不将其中一种方法设为“调度程序方法”的情况下解决这种情况。有没有办法让一种方法成为解决这种歧义的“更糟糕的候选者”?


更新

似乎不清楚我真正想要实现的目标。所以这里有一些带有注释的“伪代码”:

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

更新

我想从你这里得到类似的东西,如何让普通函数成为变差函数的更差候选者。

例如:

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

有什么东西可以使可变参数功能变得更糟吗?

背景:目前我有以下宏,只是为了模拟我想要的使用。

#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)

它适用于“正常”功能 - 但“宏生成功能”并不被认为更糟......

4

1 回答 1

4

由于您标记了此 C++20,因此您可以使用 requires 子句来限制B::Do其中之一B::DoA::Do可调用,然后if constexpr在正文中使用:

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

在这里,我使用true代替B::Do可调用的条件,因此只需在两个地方适当地替换该条件即可。

B::Do您可以通过将实际实现隐藏在其他一些函数中来减少重复:

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

现在你只需要约束B::DoImpl


一种不同的方法仍然是使用类似 Boost.Hof 的first_of()适配器的东西(因为这就是你想要做的 - 调用一系列函数中的第一个)。这对成员函数来说有点尴尬,但你可以让它与私有静态成员一起工作:

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...);
    }
};
于 2020-07-05T14:44:17.507 回答