在类创建时根据模板参数更改类API

时间:2018-04-19 10:27:21

标签: c++ c++11 templates sfinae

我正在寻找创建一个在特定模板实例化下会暴露不同API的类。它具有常见功能,但在用户将使用该类的特定实例化的情况下应禁用一些功能。像这样:

VarApi<T1> v1;
v1.common();
v1.funcA1();
// v1.funcA2(); // ERROR
v1.funcA1_2();

VarApi<T2> v2;
v1.common();
// v2.funcA1(); // ERROR
v2.funcA2();
v2.funcA1_2();

VarApi<T3> v3;
v3.common();
// v2.funcA1(); // ERROR
// v2.funcA2(); // ERROR
// v1.funcA1_2(); // ERROR

我发现您可以使用SFINAEstd::enable_if来实现这一目标:

enum Type { T1, T2, T3 };

template <Type TType> struct VarApi {
    void common() { }

    template <Type T = TType,
        typename = typename std::enable_if<T == T1>::type>
    void funcA1() { }

    template <Type T = TType,
        typename = typename std::enable_if<T == T2>::type >
    void funcA2() { }

    template <Type T = TType,
        typename = typename std::enable_if<T == T1 || T == T2>::type >
    void funcA1_2() { }

    template <Type T = TType,
        typename = typename std::enable_if<T == T3>::type >
    void funcA3() { }
};

这可以实现并实现上述功能。问题是用户仍然可以通过以下方式覆盖它:

VarApi<T2> v2;
v2.funcA1<T1>(); // NOT ERROR

有没有办法防止这种情况发生?

4 个答案:

答案 0 :(得分:1)

您可以利用继承来提供所需的功能。使用CRTP,您可以通过<!-- mobile menu --> <div id="menus" class="mobile-menu"> <ul class="menu-list"> <li class="menu-item"> <a class="menu-link" href="products.html">PRODUCTS</a> </li> <li class="menu-item"> <a class="menu-link" href="sustainability.html">SUSTAINABILITY</a> </li> <li class="menu-item"> <a class="menu-link" href="compliance.html">COMPLIANCE</a> </li> <li class="menu-item"> <a class="menu-link" href="investors.html">INVESTORS</a> </li> <li class="menu-item"> <a class="menu-link" href="about.html">ABOUT</a> </li> <li class="menu-item"> <a class="menu-link" href="contact.html">CONTACT</a> </li> </ul> </div> <!-- End mobile menu --> <a href="" id="myBtn" class="humburger d-lg-none d-md-none"> aaaaaa </a>指针访问func_provider中原始类的功能。

self

编辑:

此版本允许用户在一个地方实现多种类型的功能,用户可以将类型组合在一起:

template<class T, class Derived> struct func_provider;

template<class Derived>
struct func_provider<int, Derived> {
    void funcA1() {
        auto self = static_cast<Derived*>(this);

        // do something with self
    }
};
template<class Derived> struct func_provider<double, Derived> { void funcA2() {} };

template<class T>
struct foo : public func_provider<T, foo<T>> {};

int main() {
    foo<int> f;
    foo<double> g;
    f.funcA1();
    // f.funcA2(); // Error
    g.funcA2();
    // g.funcA1(); // Error
}

答案 1 :(得分:1)

  

这可以实现并实现上述功能。问题是用户仍然可以通过以下方式覆盖它:

VarApi<T2> v2;
v2.funcA1<T1>(); // NOT ERROR
     

有没有办法防止这种情况发生?

不确定

您可以强制TTType属于同一类型

template <Type T = TType,
    typename = typename std::enable_if<
                           std::is_same<T, T1>::value
                        && std::is_same<T, TType>::value>::type>
void funcA1() { }

这可以防止模板&#34;劫持&#34;。

答案 2 :(得分:0)

解决方案1 ​​

实现您要求的一种方法是使用tempalte specialization和依赖基类来提供可选功能。

// I'm using E for enum. I find TType a bit misleading, since T usually stands for Type
template< Type EType > 
struct VarApiBase { }; // empty by default

template< >
struct VarApiBase<T1> {
  void funcA1() { }
};

template< >
struct VarApiBase<T2> {
  void funcA2() { }
};

template <Type TType>
struct VarApi : VarApiBase<TType> {
  void funcA1_2() { }
};

template <>
struct VarApi<T3> { };

我并不特别喜欢这个解决方案。因为提供共享函数变得很复杂(我把funcA1_2放在VarApi中,而不是放在基础中,然后专门的VarApi再次为T3禁用它,但是这会强制你在每次添加新的时明确地专门化EType值。您可以通过专门化的启动器来解决它,但如果您有更复杂的共享,它又会变得复杂。)

如果您需要,可以通过VarApiBase将其声明为朋友来VarApi VarApi {/ 1}}。

解决方案2

作为所有这些的廉价替代品,您可以在功能中添加static_assert

template <Type ETypeInner = EType >
void funcA1_2() {
  static_assert(ETypeInner==EType);
  static_assert(EType == T1 || EType == T2);
}

如果您确实需要SFINAE,您仍然可以将==T1 || ==T2条件放在模板中

template <Type ETypeInner = EType,
  typename = typename std::enable_if<ETypeInner == T1 || ETypeInner == T2>::type >
void funcA1_2() {
  static_assert(ETypeInner==EType);
}

但要注意它会使编译速度变慢。

解决方案3

可能最简洁的方法是拥有明确的专业化和实用功能。

在VarApi.h中:

struct VarApiImpl;

template< Type EType > 
struct VarApi; // undefined

// Ideally, VarApiCommon shouldn't need to be a template
template< Type EType >
struct VarApiCommon {
  // you can put here members and functions which common to all implementations, just for convenience.
  void common() { /* ... */ }
private:
  // You can do this if you need access to specialization-specific members.
  // Ideally, if a function is common, it should only need common members, though.
  VarApi<EType> & Derived() { return static_cast<VarApi<EType>&>(*this); }
  VarApi<EType> const& Derived() const { return static_cast<VarApi<EType> const&>(*this); }
};

template<> 
struct VarApi<T1> : VarApiCommon<T1> {
  friend VarApiImpl;
  friend VarApiCommon<T1>;
  void funcA1();
  void funcA1_2();
};

template<> 
struct VarApi<T2> : VarApiCommon<T2> {
  friend VarApiImpl;
  friend VarApiCommon<T2>;
  void funcA2();
  void funcA1_2();
};

template<> 
struct VarApi<T3> : VarApiCommon<T3> {
  friend VarApiCommon<T3>;
};

在VarApi.cpp中:

struct VarApiImpl final {
  // Here go the functions which are only shared by some specializations
  template< Type EType >
  static void funcA1_2(VarApi<EType>& vapi) {
  // Just for sanity. Since this function is private to the .cpp, it should be impossible to call it inappropriately
    static_assert(EType==T1 || EType==T2);
    // ...
  }
};
void VarApi<T1>::funcA1() { /* ... */ }
void VarApi<T1>::funcA1_2() { VarApiImpl::funcA1_2(*this); }

void VarApi<T2>::funcA2() { /* ... */ }
void VarApi<T2>::funcA1_2() { VarApiImpl::funcA1_2(*this); }

它变得像C ++一样冗长,但至少你有明确的界面清楚地说明提供了什么和不提供什么,而不必阅读一堆enable_if。< / p>

解决方案4

最终,我建议您更仔细地查看自己的要求,看看它们是否能够根据每个枚举值所代表的功能表达为正确的类层次结构。如果你需要避免重复的基础,C ++甚至有虚拟继承。例如,在您的示例中可以实现:

struct VarApiCommon {
  void common();
};

struct VarApi12 : VarApiCommon {
  void funcA1_2();
};

template< Type EType >
struct VarApi; // undefined

template<>
struct VarApi<T1> : VarApi12 {
  void funcA1();
};

template<>
struct VarApi<T2> : VarApi12 {
  void funcA2();
};

template<>
struct VarApi<T2> : VarApiCommon {
  void funcA3();
};

例如,如果你有一个funcA2_3,你仍然可以这样做:

struct VarApiCommon {
  void common();
};

struct VarApi12 : virtual VarApiCommon {
  void funcA1_2();
};

struct VarApi23 : virtual VarApiCommon {
  void funcA2_3();
};

template< Type EType >
struct VarApi; // undefined

template<>
struct VarApi<T1> : VarApi12 {
  void funcA1();
};

template<>
struct VarApi<T2> : VarApi12, VarApi23 {
  void funcA2();
};

template<>
struct VarApi<T2> : VarApi23 {
  void funcA3();
};

很大程度上取决于成员。

答案 3 :(得分:0)

我的建议是基于您能够提供实施,但希望隐藏它。

有一个实现所有内容的基础实现

template <class X> class Base
{
public:
    void A();
    void B();
    void C();
    void D();
    void E();
};

有一个继承受保护的派生类,但随后从基础

公布所有常用方法
template <class X> class Mid: protected Base<X>
{
public:
    using Base::A;
    using Base::B;
    using Base::C;
    // D & E are contentious
};

具有实际发布的类,其中每个变体T1,T2,T3是专用的 这些类都公开继承自第二类,但随后公众朋友发布了他们支持的方法。

template <class X> class Top: public Mid<X> {};
template <> class Top<X1>: public Mid<X1>
{
public:
    using Base::D;
    // Can't get E
};
template <> class Top<X2>: public Mid<X2>
{
public:
    // Can't get D
    using Base::E;
};

收益:无法访问您要隐藏的方法。没有模板功能魔术。

损失:发布规则是任意的,而不是由可读的“可读”驱动的。完全是FINAE。您也可以轻松地使用继承来构建规则,尽管您可以执行LikeX第二个模板参数。