我希望创建一个在特定模板实例化下会公开不同 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
我发现你可以通过这样的SFINAE
和std::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
有没有办法防止这种情况?
这有效并实现了上述功能。问题是用户仍然可以通过以下方式覆盖它:
VarApi<T2> v2; v2.funcA1<T1>(); // NOT ERROR
有没有办法防止这种情况?
确定。
您可以强制规定T
和TType
是同一类型
template <Type T = TType,
typename = typename std::enable_if<
std::is_same<T, T1>::value
&& std::is_same<T, TType>::value>::type>
void funcA1() { }
这可以防止模板"劫持"。
您可以利用继承来提供所需的函数。使用 CRTP,您可以通过self
指针访问func_provider
中原始类的功能。
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
实现所需目标的一种方法是使用临时专用化和依赖基类来提供可选功能。
// 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_if
。
解决方案 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();
};
很大程度上取决于成员。
我的建议是基于您能够提供实现,但想要隐藏它。
有一个基本的实现,它实现了一切
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 第二个模板参数。