用户定义的基本类型转换可以删除吗?

zjy*_*qs 5 c++ operator-overloading language-lawyer implicit-conversion

template<typename Integral>
struct IntegralWrapper {
    Integral _value;

    IntegralWrapper() = default;

    IntegralWrapper(Integral value)
        : _value(value) {}
    
    operator Integral() const {
        return _value;
    }

    operator bool() const = delete;
};

int main() {
    IntegralWrapper<int> i1, i2;
    i1 * i2;
}
Run Code Online (Sandbox Code Playgroud)

gcc 编译成功,但 MSVC 和 clang 编译失败,错误overloaded operator '*' is ambiguous。问题来自于显式删除operator bool

https://godbolt.org/z/nh6M11d98


哪一方(gcc 或 clang/MSVC)是正确的?为什么?

use*_*522 6

首先:删除函数并不会阻止在重载决策中考虑它(有一些与此处无关的小例外)。唯一的影响= delete是,如果通过重载决策选择转换函数,则程序将格式错误。


对于重载解析:

*对于所有升级的算术类型对,都有候选的内置运算符重载。

所以,除了使用*我们还可以考虑

auto mul(int a, int b) { return a*b; } // (1)
auto mul(long a, long b) { return a*b; } // (2)
// further overloads, also with non-matching parameter types

mul(i1, i2);
Run Code Online (Sandbox Code Playgroud)

值得注意的是,没有重载,包括bool, 因为bool被提升为int

对于(1)两个参数所选择的转换函数,都是operator int() const从 from 实例化的,因为从tooperator Integral() const的转换比to更好。(或者至少这似乎是意图,请参阅https://github.com/cplusplus/draft/issues/2288在重载解析中,选择使用不明确转换序列的函数是否必然导致调用格式不正确?)。intintboolint

然而(2),从intbool到的转换都不long比另一个更好。因此,出于重载解析的目的,隐式转换序列将是不明确的转换序列。该转换序列被认为与所有其他用户定义的转换序列不同。

然后当比较哪个重载更好时,不能认为两者都比另一个更好,因为两者都对两个参数都使用用户定义的转换序列,但所使用的转换序列不具有可比性。

因此,重载解析应该会失败。如果我完成了上面开始的内置运算符重载列表,则什么都不会改变。同样的逻辑适用于所有这些。

所以 MSVC 和 Clang 的拒绝是正确的,GCC 的接受是错误的。有趣的是,我上面给出的函数的明确示例 GCC 确实按预期拒绝了。


要禁止隐式转换,bool您可以使用约束转换函数模板,该模板不允许在用户定义的转换之后使用另一个标准转换序列:

template<std::same_as<int> T>
operator T() const { return _value; } 
Run Code Online (Sandbox Code Playgroud)

这将只允许转换为int. 如果您无法使用 C++20,则需要通过 .SFINAE 替换该概念std::enable_if