根据 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
我发现您可以像这样使用 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
有什么办法可以避免这种情况吗?
您可以利用继承来提供所需的功能。使用 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_if
s。
解决方案 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?
当然可以。
你可以强加 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() { }
这可以防止模板“劫持”。
我的建议是基于你能够提供实现,但想隐藏它。
有一个基础实现,它实现了一切
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 第二个模板参数。
我想创建一个 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
我发现您可以像这样使用 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
有什么办法可以避免这种情况吗?
您可以利用继承来提供所需的功能。使用 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_if
s。
解决方案 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?
当然可以。
你可以强加 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() { }
这可以防止模板“劫持”。
我的建议是基于你能够提供实现,但想隐藏它。
有一个基础实现,它实现了一切
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 第二个模板参数。