C++使用参数包的声明



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

想象一下以下代码:

template<typename... Bases>
class SomeClass : public Bases...
{
public: 
using Bases::DoSomething...;
void DoSomething(){
//this is just another overload
}
};

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

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

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

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

然后,当T具有任何类型的DoSomething过载时,我们可以精确地在混合上调用DoSomething()。那就是:

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

确实满足CCD_ 11。

然后我们使用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();
};

不管BasesDoSomething重载是什么样子,它都能正常工作,并且在我提到的第一种情况下正确地执行重载解析。

演示

有条件地使用回退怎么样?

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

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

示例。

您可以添加包装器,该包装器通过转发而不是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的存在(过载是主要问题(。巴里解决了这个问题,所以你有更好的选择。

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

#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仅支持非重载方法(每个基类(;关于这一点的概括,请参阅巴里的回答。

最新更新