c++ - 可变函数重载和 SFINAE - 解决歧义以模拟 "hide by signature"

标签 c++ overloading variadic-functions c++20 name-hiding

我想在 C++ 中使用“按签名隐藏”而不是“按名称隐藏”。所以我写了一个宏,它定义了一个可变参数函数,如果存在的话,它将所有调用委托(delegate)给它的基类。
我不能使用 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)
它适用于“正常”功能 - 但“宏生成功能”并不被认为更糟......

最佳答案

由于你标记了这个 C++20,你可以使用 requires 子句来约束 B::DoB::Do 上或 A::Do可调用,然后使用 if constexpr在 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...);
        }
    }
};
这里我使用 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() 的方法。适配器(因为这就是你想要做的 - 调用一系列函数中的第一个)。这对成员函数来说有点尴尬,但你可以让它与私有(private)静态成员一起工作:
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...);
    }
};

关于c++ - 可变函数重载和 SFINAE - 解决歧义以模拟 "hide by signature",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62739212/

相关文章:

c++ - void() 和 int() 解析的区别

c++ - 在 Windows 中从文件名获取驱动器号

c++ - 从重载函数中选择常量表达式?

c++ - 模板函数重载

gcc - va_list 尚未声明

Java 方法重载和可变参数

c++ - 混合别名和模板特化

c++ - 为什么我的 C++ 代码比 R 慢那么多?

c++ - 如何部分重载 C++ 模板子类中的虚函数?

c - 变量参数不打印