根据 class 创建时的模板参数更改 class API

Change class API depending on template parameter at class creation

我想创建一个 class,在特定的模板实例化下会公开一个不同的 API。它具有常用功能,但在用户将使用 class 的特定实例的情况下应禁用一些功能。像这样:

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

有什么办法可以避免这种情况吗?

您可以利用继承来提供所需的功能。使用 CRTP,您可以通过 self 指针访问 func_provider 中原始 class 的功能。

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
}

编辑:

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

template<class... Ts> struct types {};

template<class Types, class T> struct is_in : public std::false_type {};
template<class... Ts, class T>
struct is_in<types<T, Ts...>, T> : public std::true_type {};
template<class... Ts, class T0, class T>
struct is_in<types<T0, Ts...>, T> : public is_in<types<Ts...>, T> {};

template<class Derived, bool B, class T> struct func_provider {};
template<class Derived, class T, class... Ts>
struct func_collector
    : public func_provider<Derived, is_in<Ts, T>::value, Ts>...
{};


// implement functions for int
template<class Derived>
struct func_provider<Derived, true, types<int>> {
    void funcA1() {
        auto self = static_cast<Derived*>(this);
        // do something with self
    }
};

// implement functions for double
template<class Derived>
struct func_provider<Derived, true, types<double>> { void funcA2() {} };

// implement functions for both int and double
template<class Derived>
struct func_provider<Derived, true, types<int, double>> { void funcA1_2() {} };

template<class T>
struct foo : public func_collector<foo<T>, T,
    // pull desired functions
    types<int>, types<double>, types<int, double>>
{
    void common() {}
};

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

解决方案 1

实现您的要求的一种方法是使用 tempalte 专业化和依赖基础 classes 来提供可选功能。

// 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 值。您可以使用专业化的启用程序来解决它,但如果您有更复杂的共享,它又会变得复杂)。

如果需要,您可以通过在 VarApi.

中声明其为好友来授予 VarApiBase 访问 VarApi 的权限

解决方案 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_ifs。

解决方案 4

最后,我建议您更仔细地查看您的要求,看看是否不能根据每个枚举值所代表的特征将它们表达为适当的 class 层次结构。如果您需要避免重复基数,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();
};

很大程度上取决于成员。

This works and achieves the functionality above. The problem is that the user can still override this with:

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

Is there a way to prevent this case?

当然可以。

你可以强加 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() { }

这可以防止模板“劫持”。

我的建议是基于你能够提供实现,但想隐藏它。

有一个基础实现,它实现了一切

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

有一个派生的 class 继承了 protected,但随后发布了 public 来自基础

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

实际发布class,其中每个变体 T1、T2、T3 都是专门的。
这些 class 都是 public 继承自第二个 class,但是 public 朋友发布了他们支持的方法。

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

收获:你想隐藏的方法是不可访问的。没有模板函数魔法。

损失:发布规则是任意的,根本不受 'readable' FINAE 的驱动。你也不能轻易地使用继承来构建规则,尽管你可以做一个 LikeX 第二个模板参数。