使用(模板化)删除的函数重载来防止通常的算术转换

时间:2017-01-25 10:03:29

标签: c++ implicit-conversion

我经常发现我希望阻止特定构造函数或函数的缩小或签名转换(通常是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
}

现在,如果我使用已删除的模板或删除的非模板功能,或者是否有重要的情况,这些情况只是一个问题吗?我发现删除的模板更明确,防止所有T ,但另一方面,当使用此模式与构造函数时;转发构造函数have their issues。 如果是模板化版本,最好是删除模板const T&吗?

1 个答案:

答案 0 :(得分:3)

首先,我认为值得注意的是,非模板版本会阻止大多数情况,因为它们会导致两个重载之间存在歧义,而模板版本通过提供比非模板重载更好的匹配来实现。因此,模板版本生成的错误消息将更加清晰,沿着“你试图调用这个被删除的函数”,而不是“我无法在这两者之间做出决定,哪一个做你真的想要吗?“从这个角度来看,模板版本看起来更好。

但是,有些情况会有不同的行为。

一个模糊的情况类似foo({f});,模板版本不会阻止这种情况,因为初始化列表使参数成为非推导的上下文,因此模板的演绎失败,只留下非模板超载。

出于类似的原因,模板版本会阻止foo({i});,但不会阻止foo({3});3是常量,因此转换为double不是缩小转化率,因为3适合double并在转换回时生成相同的值。非模板版本阻止了两者,因为它们都是模棱两可的。

另一个案例:

enum E : unsigned { };

int main() 
{
    E e{};
    bar(e);
}

模板版本通过提供最佳过载来防止这种情况。非模板的不是,因为Eunsigned int是促销,这比Eint更好,这是转化。

类似的问题会出现在平台上,例如,short的大小与int的大小相同,也适用于来自char16_tchar32_t或{{的转化的平台1}},取决于他们特定于平台的表示。

尽管对于问题的背景可能不那么有趣,但另一个区别似乎是:

wchar_t

模板版本通过提供最佳重载来防止这种情况,而非模板版本则不会(调用struct A { operator double() { return 7.0; } }; int main() { A a{}; foo(a); } )。