3

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

想象一下下面的代码:

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

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

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

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

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

4

4 回答 4

4

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

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

保留重载决议的唯一方法是使用继承。

那里的关键是完成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, ...); };

并使其成为可变参数,以使其最小化。但其实无所谓。现在,如果我们引入一种新类型,例如:

template <typename T> struct Hybrid : Fallback<T>, T { };

然后我们可以在没有任何类型的重载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
{ };

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

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

struct C {
    void DoSomething(int);
    void DoSomething(int, int);
};

确实满足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();
};

无论Bases' 的所有DoSomething重载是什么样的,这都能正常工作,并且在我提到的第一种情况下正确执行重载解析。

演示

于 2020-04-06T03:01:12.983 回答
2

您可以通过转发而不是添加处理基本情况的包装器using

template <typename T>
struct Wrapper : T
{
    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args) const
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args)
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    // You might fix missing noexcept specification
    // You might add missing combination volatile/reference/C-elipsis version.
    // And also special template versions with non deducible template parameter...
};

template <typename... Bases>
class SomeClass : public Wrapper<Bases>...
{
public: 
  using Wrapper<Bases>::DoSomething...; // All wrappers have those methods,
                                        // even if SFINAEd

  void DoSomething(){ /*..*/ }
};

演示

正如 Barry 所指出的,随着重载分辨率的改变,还有其他缺点,使得一些调用模棱两可......

注意:我提出了这个解决方案,因为我不知道如何创建正确的特征来检测DoSomething所有情况下的存在(主要是重载问题)。巴里解决了这个问题,所以你有更好的选择。

于 2020-04-05T15:17:09.823 回答
2

有条件地使用后备怎么样?

创建每个方法的不可调用实现:

template<class>
struct Fallback {
    template<class..., class> void DoSomething();
};

Fallback为每个基类继承一次:

class SomeClass : private Fallback<Bases>..., public Bases...

然后有条件地从基类或其各自的后备中拉入每个方法:

using std::conditional_t<HasDoSomething<Bases>::value, Bases, Fallback<Bases>>::DoSomething...;

例子

于 2020-04-05T22:14:52.857 回答
0

只要您愿意使用别名模板来命名您的类,您就可以在没有额外基类的情况下实现这一点。诀窍是根据谓词将模板参数分成两个包

#include<type_traits>

template<class,class> struct cons;  // not defined
template<class ...TT> struct pack;  // not defined

namespace detail {
  template<template<class> class,class,class,class>
  struct sift;
  template<template<class> class P,class ...TT,class ...FF>
  struct sift<P,pack<>,pack<TT...>,pack<FF...>>
  {using type=cons<pack<TT...>,pack<FF...>>;};
  template<template<class> class P,class I,class ...II,
           class ...TT,class ...FF>
  struct sift<P,pack<I,II...>,pack<TT...>,pack<FF...>> :
    sift<P,pack<II...>,
         std::conditional_t<P<I>::value,pack<TT...,I>,pack<TT...>>,
         std::conditional_t<P<I>::value,pack<FF...>,pack<FF...,I>>> {};

  template<class,class=void> struct has_something : std::false_type {};
  template<class T>
  struct has_something<T,decltype(void(&T::DoSomething))> :
    std::true_type {};
}

template<template<class> class P,class ...TT>
using sift_t=typename detail::sift<P,pack<TT...>,pack<>,pack<>>::type;

然后分解结果并从各个类继承:

template<class> struct C;
template<class ...MM,class ...OO>  // have Method, Others
struct C<cons<pack<MM...>,pack<OO...>>> : MM...,OO... {
  using MM::DoSomething...;
  void DoSomething();
};

template<class T> using has_something=detail::has_something<T>;

template<class ...TT> using C_for=C<sift_t<has_something,TT...>>;

请注意,has_something为简单起见,此处仅支持非重载方法(每个基类);请参阅巴里的回答以概括这一点。

于 2020-04-06T14:15:42.357 回答