我正在使用C++ 11,并试图在我的应用程序中建立一个通用的Handle
类,在该类中有时可以转换具有不同基础类型的句柄,但前提是这些基础类型与祖先/后代相关,否则尝试转换应该只是失败。我还需要一个永不失败的函数,该函数会告诉我是否可以在两种类型之间进行转换。特别是,我不希望基础类型尝试对不在其祖先/后代行中的类型进行任何转换,因此我在考虑是否在 bool(boolean) 值上定义了模板函子,该 bool(boolean) 值在编译时告诉我是否类型是相关的,如果不相关,则使用模板特殊化来拒绝转换,如果相关,则将转换请求转发到基础类型。每个基类都包含一个模板化的转换函数,该函数知道如何转换为其层次结构中的每个对应类型;还包括一个模板化的 bool(boolean) 函数,该函数根据类实例的内部状态指示是否可以进行这种转换。
我放在一起的样子是这样的:
template<class T>
class MyHandle {
public:
...
template<bool> struct can_be_ref {
template<class U> bool operator()(const MyHandle *, const U*) const
{
}
};
template<bool> struct as_ref {
template<class U> MyHandle<U> operator()(const MyHandle *, const U*) const
{
throw std::runtime_error("Illegal type conversion");
}
};
template<class U> bool can_be();
template<class U> MyHandle<U> as();
private:
const T* get_member_reference() const;
};
template<class T> struct MyHandle<T>::can_be_ref<true> {
template<class U> bool operator()(const MyHandle<T> *ptr, const U*)
{
ptr->get_member_reference()->can_be<U>();
}
};
template<class T> struct MyHandle<T>::as_ref<true> {
template<class U> MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const
{
return ptr->get_member_reference()->as<U>();
}
};
template<class T> template<class U> bool MyHandle<T>::can_be()
{
return can_be_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value > ()(this, reinterpret_cast<const U *> (nullptr));
}
template<class T> template<class U> MyHandle<U> MyHandle<T>::as()
{
return as_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value > ()(this, reinterpret_cast<const U *> (nullptr));
}
但是,这不会编译,而且我不知道自己在做什么错。失败发生在我尝试专门化
can_be_ref
和as_ref
结构的情况下,其中编译器抱怨模板参数列表太少。希望我要做的事情在我提供的解释和令人遗憾的这段代码之间没有任何作用,但这是我想到的唯一方法。我究竟做错了什么?
编辑:
澄清一下,假设我具有以下类层次结构:
class A {
public:
template<class U> bool can_be();
template<class U> MyHandle<U> as();
...
};
class B : public A{
...
};
class C {
public:
template<class U> bool can_be();
template<class U> MyHandle<U> as();
...
};
每个层次结构都定义了一个
can_be
和as
方法,该方法仅涉及其层次结构中的项,特别是在某些情况下,如果模板的参数类型不正确,可能会导致编译器错误,这就是为什么该类型必须在编译时检查。并假设我们定义了以下变量:
MyHandle<A> a;
MyHandle<B> b;
MyHandle<C> c;
因为
a
和b
是相关类型,所以A::can_be
和A::as
可以在它们之间自由使用,但是A::can_be可能会产生编译器错误。因此,例如MyHandle中围绕它们的包装将其隐藏,以便MyHandle<A>::can_be<C>()
aways返回false。尽管MyHandle<B>::as<C>()
总是会引发异常,但甚至不尝试生成对B::as<C>
的调用,因为这可能会导致编译错误。编辑:
根据下面Kamil的建议,解决方案是将模板定义映射到周围的类。我所做的就是创建如下的帮助器模板:
template<class T,class U,bool> class MyHandleConverter
{
public:
inline MyHandleConverter(const MyHandle<T> *) { }
inline bool can_be() const { return false; }
inline MyHandle<U> as() const { return MyHandle<U>(nullptr); }
};
我决定放弃在无效转换上引发异常,现在MyHandle的每个实例都包含一个称为
value
的void指针,该指针可以包含一个指向有关实际基础类型的更多信息的指针,如果无效,则为nullptr,因此我可以创建MyHandleConverterClass的部分特化,如下所示:template<class T,class U> class MyHandleConverter<T,U,true> {
public:
inline MyHandleConverter(const MyHandle<T> *ref):reference(ref) { }
inline bool can_be() const {
if (std::is_base_of<T,U>::value) {
return true;
} else if (reference->value == nullptr) {
return false;
} else {
return reference->underlying_can_be((const U*)(nullptr));
}
}
inline MyHandle<U> as() const {
if (std::is_base_of<U,T>::value) {
return MyHandle<U>(reference->value);
} else if (reference->value == nullptr) {
return MyHandle<U>(nullptr);
} else {
return reference->underlying_as((const U*)(nullptr));
}
}
private:
const MyHandle<T> *reference;
};
而不是像以前那样抛出异常,而是返回一个无效的MyHandle(它具有特殊的构造函数
MyHandle(nullptr_t)
,并且可以通过一个简单的boolean MyHandle
方法查询is_valid()
的状态(以及如果有必要,则调用者可以抛出的异常)这是所需要的,对我而言,这比编写as<U>
函数在失败时抛出异常本身要少写try .... catch块。MyHandle类具有模板化的
underlying_can_be
方法和模板化的underlying_as
方法,它们分别将其请求分别转发到基础类类型的can_be
或as
方法。值得注意的是,如果不通过MyHandleConverter<T,U,true>
类调用这些方法,即使编译器也不会生成这些方法,因此现在编写了MyHandle的can_be
和as
方法:template <class T> template<class U> bool MyHandle<T>::can_be() const {
return MyHandleConverter<T, U, are_related_handle_types<U,T>()>(this).can_be();
}
template<class T> template<class U> MyHandle<U> MyHandle<T>::as() const {
return MyHandleConverter<T, U, are_handle_types_related<U,T>()>(this).as();
}
其中
are_handle_types_related
是模板化的constexpr函数,如果发现调用基础模板化类型紧密相关,以至于调用MyHandle的can_be
或has
方法的基础类型不会导致编译器错误,或者在某些情况下是逻辑错误,则返回true在编译时甚至在运行时都无法检测到的问题,而无需在每种底层类型as
和can_be
方法中编写复杂的检测逻辑,只需检测两个类各自是从适当的类型派生而使转换过程合理地成功就可以检测到的。这样,当
are_handle_types_related
检测到类型不兼容,并且调用相应类型的can_be
或as
方法无效时,创建的MyHandleConverter
实例为MyHandleConverter<T,U,false>
,它不会尝试调用基础类类型,而MyHandleConverter<T,U,true>
可以,但是只会对已经被发现可以调用基础类型的适当对话函数的类实例化。 最佳答案
要专门化模板,您必须在专门化之前添加template
关键字,例如:
template<class T> // Template parameter for 'MyHandle<T>'
template<> // No unspecialized template parameters for 'can_be_ref', but indicate that it is a template anyway
struct MyHandle<T>::can_be_ref<true>
{
template<class U> bool operator()(const MyHandle<T> *ptr, const U*)
{
ptr->get_member_reference()->can_be<U>();
}
};
但是,这也不编译。根据http://en.cppreference.com/w/cpp/language/template_specialization:
因此,我们也不能完全不对MyHandle进行专门化而对它进行专门化。解决方案可能是模板参数的部分特化-将参数
U
从can_be_ref::operator()
移到can_be_ref
级别:template<class T>
class MyHandle
{
public:
...
template<class U, bool>
struct can_be_ref
{
bool operator()(const MyHandle<T> *ptr, const U*) const
{
return false;
}
};
template<class U, bool>
struct as_ref
{
MyHandle<U> operator()(const MyHandle<T> *, const U*) const
{
throw std::runtime_error("Illegal type conversion");
}
};
...
};
然后我们可以进行部分特化:
template<class T>
template<class U>
struct MyHandle<T>::can_be_ref<U, true>
{
bool operator()(const MyHandle<T> * ptr, const U*) const
{
return ptr->get_member_reference()->can_be<U>();
}
};
template<class T>
template<class U>
struct MyHandle<T>::as_ref<U, true>
{
MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const
{
return ptr->get_member_reference()->as<U>();
}
};
template<class T>
template<class U> bool MyHandle<T>::can_be() const
{
return can_be_ref<U,
std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
this, nullptr);
}
template<class T>
template<class U> MyHandle<U> MyHandle<T>::as()
{
return as_ref<U,
std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
this, nullptr);
}
实际上,当我将其编译为示例类
A
,B
,C
时,在return ptr->get_member_reference()->can_be<U>();
行上抱怨:expected primary-expression before ')' token
。我真的不明白这里有什么问题。像get_member_reference()->A::can_be<U>()
一样调用它。一种可行的解决方法是通过传递U
类型的参数来确定can_be<U>()
的U
参数:class A {
public:
template<class U> bool can_be(const U*)
{
return can_be<U>();
}
template<class U> MyHandle<U> as(const U*)
{
return as<U>();
}
template<class U> bool can_be();
template<class U> MyHandle<U> as();
};
template<class T>
template<class U>
struct MyHandle<T>::can_be_ref<U, true>
{
bool operator()(const MyHandle<T> * ptr, const U* uptr) const
{
return ptr->get_member_reference()->can_be(uptr);
}
};
template<class T>
template<class U>
struct MyHandle<T>::as_ref<U, true>
{
MyHandle<U> operator()(const MyHandle<T> *ptr, const U* uptr) const
{
return ptr->get_member_reference()->as(uptr);
}
};