C++ 使用参数包声明

Ber*_*nns 3 c++ using-declaration name-hiding c++17 c++20

我想定义一个类,它继承了一堆类,但不隐藏这些类的某些特定方法。

想象一下下面的代码:

template<typename... Bases>
class SomeClass : public Bases...
{
public: 
  using Bases::DoSomething...;

  void DoSomething(){
    //this is just another overload
  }
};

Run Code Online (Sandbox Code Playgroud)

现在的问题是,如果只有一个类没有具有该名称的成员,DoSomething我会收到错误。我已经尝试过使用宏和 SFINAE 来模拟“如果未定义则忽略”,但要处理所有情况,这会变得非常大且丑陋!你有什么办法解决这个问题吗?

如果我可以定义:“嘿使用 - 忽略缺失的成员”,那就太好了。

这里我有一些示例代码:Godbolt

Bar*_*rry 5

Jarod42 方法的问题在于,您改变了重载解析的外观 - 一旦您将所有内容都设为模板,那么所有内容都是完全匹配的,并且您无法再区分多个可行的候选者:

struct A { void DoSomething(int); };
struct B { void DoSomething(double); };
SomeClass<A, B>().DoSomething(42); // error ambiguous
Run Code Online (Sandbox Code Playgroud)

保留重载解析的唯一方法是使用继承。

关键是完成ecatmur开始的事情。但HasDoSomething看起来是什么样子呢?链接中的方法仅在存在单个、非重载、非模板的情况下才有效。但我们可以做得更好。我们可以使用相同的机制来检测是否DoSomething存在需要开头的机制using:来自不同范围的名称不会过载。

因此,我们引入了一个新的基类,它有一个DoSomething永远不会被真正选择的基类 - 我们通过创建我们自己的显式标记类型来实现这一点,我们是唯一会构造的标记类型。由于没有更好的名字,我将以我的狗(Westie)的名字来命名它:

struct westie_tag { explicit westie_tag() = default; };
inline constexpr westie_tag westie{};
template <typename T> struct Fallback { void DoSomething(westie_tag, ...); };
Run Code Online (Sandbox Code Playgroud)

并使其成为可变的,以达到良好的效果,只是为了使其最少。但其实并不重要。现在,如果我们引入一种新类型,例如:

template <typename T> struct Hybrid : Fallback<T>, T { };
Run Code Online (Sandbox Code Playgroud)

然后,我们可以在没有任何类型的过载时DoSomething()精确地调用混合体。那是:TDoSomething

template <typename T, typename=void>
struct HasDoSomething : std::true_type { };
template <typename T>
struct HasDoSomething<T, std::void_t<decltype(std::declval<Hybrid<T>>().DoSomething(westie))>>
    : std::false_type
{ };
Run Code Online (Sandbox Code Playgroud)

请注意,通常在这些特征中,主要特征是false,专业化特征是true——这里相反。这个答案和 ecatmur 的关键区别在于,回退的重载必须仍然可以以某种方式调用 - 并使用该功能来检查它 - 只是它不会对于用户实际使用的任何类型实际上是可调用的。

通过这种方式检查可以让我们正确检测到:

struct C {
    void DoSomething(int);
    void DoSomething(int, int);
};
Run Code Online (Sandbox Code Playgroud)

确实满足了HasDoSomething

然后我们使用 ecatmur 展示的相同方法:

template <typename T>
using pick_base = std::conditional_t<
    HasDoSomething<T>::value,
    T,
    Fallback<T>>;

template<typename... Bases>
class SomeClass : public Fallback<Bases>..., public Bases...
{
public: 
  using pick_base<Bases>::DoSomething...;

  void DoSomething();
};
Run Code Online (Sandbox Code Playgroud)

无论所有BasesDoSomething重载是什么样子,这都有效,并且在我提到的第一种情况下正确执行重载解析。

演示