我经常发现我想阻止特定构造函数或函数的缩小或符号转换(通常是 Usual arithmetic conversions )。我倾向于写:
#include <iostream>
void foo(double f){
std::cout << "foo double" << f <<std::endl;
}
void foo(float) = delete;
// or template<typename T> void foo(T&& f) = delete;
void bar(unsigned int f){
std::cout << "bar uint " << f <<std::endl;
}
void bar(signed int ) = delete;
// or template<typename T> void bar(T&& f) = delete;
这样就可以了...
int main() {
auto i=2;
auto d=2.0;
auto f=2.0f;
foo(i); // prevented
foo(d); // OK
foo(f); // prevented
auto uil = 3ull;
auto ul = 3ul;
auto u = 3u;
bar(i); // prevented
bar(d); // prevented
bar(f); // prevented
bar(uil); // prevented
bar(ul); // prevented
bar(u); // OK
}
现在,在这些情况下,如果我使用已删除的模板或已删除的非模板函数,这只是一个品味问题,还是在某些情况下很重要?我发现删除的模板更明确,可以防止 all T,但另一方面,当将此模式与构造函数一起使用时;转发构造函数have their issues .
如果是模板化版本,将删除的模板设为 const T&
会更好吗?
最佳答案
首先,我认为值得注意的是,非模板版本可以防止大多数情况,因为它们会导致两个重载之间产生歧义,而模板版本通过提供比非模板重载更好的匹配来做到这一点。正因为如此,模板版本生成的错误消息将趋于更加清晰,类似于“你试图调用这个已删除的函数”,而不是“我无法在这两者之间做出决定,哪一个可以你真的想要吗?”。从这个角度来看,模板版本看起来更好。
但是,在某些情况下,情况会有所不同。
一个模糊的情况是像foo({f});
,它没有被模板版本阻止,因为初始化列表使参数成为一个非推导的上下文,所以推导失败模板,只留下非模板重载。
出于类似的原因,模板版本将阻止 foo({i});
但不会阻止 foo({3});
( 3
是一个常量,因此到 double
的转换不是收缩转换,因为 3
适合 double
并生成转换回来时的相同值)。非模板版本会阻止两者,因为它们都模棱两可。
另一种情况:
enum E : unsigned { };
int main()
{
E e{};
bar(e);
}
模板版本通过提供最好的重载来防止这种情况。非模板的没有,因为E
到unsigned int
是一种提升,比E
到int<要好
,这是一个转换。
类似的问题会出现在平台上,例如,short
与 int
的大小相同,以及来自 char16_t
的转换, char32_t
或 wchar_t
,具体取决于它们特定于平台的表示形式。
虽然对于问题的上下文可能不太有趣,但另一个区别出现在:
struct A
{
operator double() { return 7.0; }
};
int main()
{
A a{};
foo(a);
}
模板版本再次通过提供最佳重载来防止这种情况,而非模板版本则不会(调用 foo(double)
)。
关于c++ - 使用(模板化的)删除函数重载来防止通常的算术转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41848592/