【问题标题】:Variadic function Overloading and SFINAE - Solve ambiguity to simulate "hide by signature"可变参数函数重载和 SFINAE - 解决歧义以模拟“隐藏签名”
【发布时间】:2020-10-25 13:59:49
【问题描述】:

我想在 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 上查看。

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


更新

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

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

它适用于“普通”函数 - 但“宏生成函数”并不被认为更糟......

【问题讨论】:

  • 你有没有想过用免费的朋友函数代替成员函数?
  • 如果您在第二次更新中删除了using A::Do;A::Do 将被B:Do 隐藏。但我猜你的实际情况更复杂,A::Do 实际上是一个可变参数模板,B::Do 是一个不同的可变参数模板。我认为你需要举一个更现实的例子。
  • 是的,这种使用不是“C++ 使用”。因此,我添加了评论来解释我想要什么。在 C++ 中,通过在子类中引入具有相同名称的新函数来隐藏所有继承的方法 - 这称为“按名称隐藏”。我的目标是仅隐藏已经可以使用子方法调用的继承函数。这称为“通过签名隐藏”。该行为应该像 CSharp 中的行为,而不是通常的 C++。在 CSharp 中它有点复杂 - 因此忽略可见性修饰符。

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


【解决方案1】:

由于您标记了此 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...);
    }
};

【讨论】:

  • 感谢巴里的回答。您的实现或多或少会导致“调度程序”功能。但我想要的是一种使可变参数函数重载变得更糟的方法。我将编辑我的问题。
  • @BerndBaumanns 我不知道这意味着什么,我也不理解您的编辑(您的 Do... 并不是更差的候选人,因为 ...,它根本不是候选人)。除非A 事先知道B 存在...然后B 是最后一个,或者C 也存在,否则你不能只使一个功能变得更糟?
  • 对不起,你是对的 - 这是一个错误。我又改了。我想定义一个宏来定义一个只有在没有更好匹配时才被选中的函数。是的,可以有 C 甚至 D...
  • A 应该对 B 一无所知。B 不应该仅仅因为 A 添加了一个名称已经在 B 中定义的方法而被更改。
  • @BerndBaumanns “只有在没有更好匹配的情况下才会被选中” - 但这与您所要求的相反,即使 A::Do() 更好,您也希望调用 B::Do()匹配。
猜你喜欢
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
相关资源
最近更新 更多