枚举和指向成员的指针
我最近尝试创建一个 is_class 类,并且需要一种方法让编译器区分枚举类型和为其定义转换运算符的类类型。鉴于类、结构和联合是唯一与指向成员函数的指针兼容的类型,我决定让编译器确定用于实例化 is_class 模板的类型是否兼容具有指向成员函数的指针。在遇到几个问题后,我决定测试枚举与指向成员的指针结合使用时的行为,并得到了一些奇怪的结果。以下片段说明了第一个怪癖:
enum ENUM {};
void Test(void (ENUM::*pmem) (void))
{
/* ... */
}
Test(NULL);
使用 Microsoft Visual C++ 2010 进行编译时,函数定义的指向成员的指针部分:(ENUM::*pmem)
以红色突出显示,并将鼠标悬停在声明显示错误:
Error: "ENUM" is not a class type
但是,编译器解析此段时没有遇到任何错误,并将 pmem
分配给 NULL
代码>.对我来说有趣的是,编译器将允许这种情况,因为枚举类型不是类、结构或联合,因此不能拥有自己的方法。
第二个有趣的点是在创建模板函数时出现的,该函数采用类型不同的指向成员参数的指针:
template<class _Ty>
void Test_Template(void (_Ty::*pmem) (void))
{
/* ... */
}
当然,为了使用此函数,必须显式限定它:
Test_Template<ENUM>(NULL);
但是,此调用会生成一个错误,说明
: >“void Test(void (__thiscall _Ty::* )(void))”的显式模板参数无效
我通过创建一个附加函数模板解决了这个问题,其原型将与任何调用相匹配无法匹配前一个模板函数的原型(其中涉及使用省略号)。
问题:
为什么枚举与指向成员的指针兼容?
为什么调用非模板
Test
函数时会出现完全匹配,而编译器会为模板Test_Template
显式限定生成错误?
I recently attempted to create an is_class
class and needed a way for the compiler to differentiate between enumeration types and class types for which conversion operators are defined. Seeing as how classes, structs and unions are the only types compatible with pointer-to-member functions, I decided to have the compiler determine if the type used to instantiate the is_class
template was, in turn, compatible with pointers-to-member functions. After running into several issues, I decided to test the behavior of enumerations when used in conjunction with pointer-to-members and got some wacky results. The following segment illustrates the first quirk:
enum ENUM {};
void Test(void (ENUM::*pmem) (void))
{
/* ... */
}
Test(NULL);
When compiling with Microsoft Visual C++ 2010, the pointer-to-member portion of the function definition: (ENUM::*pmem)
is highlighted in red and mousing over the declaration reveals the error:
Error: "ENUM" is not a class type
However, the compiler parses this segment without encountering any errors, assigning pmem
to NULL
. It is interesting to me that the compiler would allow this seeing as how enumeration types are not classes, structs or unions and therefore cannot possess methods of their own.
The second point of interest arose when creating a template function, taking a pointer-to-member argument whose type varies:
template<class _Ty>
void Test_Template(void (_Ty::*pmem) (void))
{
/* ... */
}
Of course in order to use this function, it must be explicitly qualified:
Test_Template<ENUM>(NULL);
This call however, generates an error stating:
invalid explicit template argument(s) for 'void Test(void (__thiscall _Ty::* )(void))'
I fixed this issue by creating an additional function template, the prototype of which would match any call that failed to match the prototype for the former template function (which involved using an ellipsis).
Questions:
Why is an enumeration compatible with pointers-to-members?
Why is there an exact match when invoking the non-template
Test
function while the compiler generates an error for the templateTest_Template
explicit qualification?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
关于你的第一个问题,编译器似乎确实报告枚举不能有成员函数,因为编译器报告函数声明错误。它可能通过在内部尝试尽可能多地纠正错误声明来让调用成功,在这种情况下,这意味着注意到您正在尝试声明类似指针的内容并允许调用。编译器并不要求该行给出错误;由于该程序是 I'll 形成的,只要编译器通过诊断拒绝该程序,就不需要到处给出错误。
至于你的第二个问题,拥有第二个模板使错误消失的原因是 " 替换失败不是错误”(SFINAE)原则。当编译器使用某些类型参数实例化函数模板时,如果它发现特定函数实例化无效(例如,尝试获取指向枚举成员的指针),它不会报告错误。相反,它只是将该模板从考虑中删除。但是,如果您编写的模板在使用给定参数实例化时均无效,则编译器将发出错误,因为它无法找到与您尝试执行的操作匹配的内容。在第一种情况下,当您只有一个模板时,会发生错误,因为 SFINAE 排除了唯一的候选模板,导致模板实例没有匹配的模板。在第二种情况下,您的“包罗万象”的模板在实例化模板后仍然有效,因此虽然排除了带有指向成员的指针的模板,但仍然有一个合法的模板可供您引用。因此,代码完全没问题。
With regards to your first question, it seems like the compiler is indeed reporting that enums can't have member functions, since the compiler is reporting an error on the function declaration. It's probably letting the call succeed by internally trying to correct the bad declaration as much as possible, which in this case means noticing that you were trying to declare something pointer-like and allowing the call. There's no requirement that the compiler give you an error on that line; since the program is I'll-formed, as long as the compiler rejects the program with a diagnostic it doesn't need to give errors everywhere.
As for your second question, the reason that having a second template makes the error go away is the "substitution failure is not an error" (SFINAE) principle. When the compiler instantiates a function template with some type arguments, if it finds that a particular function instantiation is invalid (for example, trying to get a pointer to a member of an enum), it doesn't report an error. Instead, it just removes that template from consideration. If, however, none of the templates you've written are valid when instantiated with the given arguments, then the compiler will issue am error because it can't find a match for what you're trying to do. In the first case, when you have just one template, the error occurs because SFINAE eliminates the only template candidate from consideration, causing the template instantition to have no matching template. In the second case, your "catch-all" template is still valid after you instantiate the template, so while the template taking a pointer-to-member is excluded, there is still a legal template that you can refer to. Consequently, the code is perfectly fine.