自定义转基础类型是否可删除?

Is user-defined conversion to fundamental-type deletable?

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;
}

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

https://godbolt.org/z/nh6M11d98


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

首先:删除一个函数并不能阻止它在重载决议中被考虑(除了一些与这里无关的小例外)。 = delete 的唯一影响是,如果通过重载决议选择转换函数,程序将是 ill-formed。


对于重载决议:

对于所有提升的算术类型对,* 运算符有候选 built-in 重载。

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

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);

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

对于 (1),为两个参数选择的转换函数是 operator int() constoperator Integral() const 实例化的,因为从 intint 的转换优于 boolint。 (或者至少这似乎是意图,参见 https://github.com/cplusplus/draft/issues/2288 and In overload resolution, does selection of a function that uses the ambiguous conversion sequence necessarily result in the call being ill-formed?)。

然而,对于 (2),从 intboollong 的转换都不比另一个好。因此,出于重载决议的目的,隐式转换序列将成为 不明确的转换序列 。此转换序列被认为不同于所有其他 user-defined 转换序列。

然后比较哪个重载更好,不能认为哪个比另一个更好,因为两者都对两个参数使用 user-defined 转换序列,但使用的转换序列不可比较。

因此重载解析应该会失败。如果我完成上面开始的 built-in 运算符重载列表,则不会有任何改变。同样的逻辑适用于所有这些。

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


要禁止隐式转换为 bool,您可以使用约束转换函数模板,它不允许在 user-defined 转换后使用另一个标准转换序列:

template<std::same_as<int> T>
operator T() const { return _value; } 

这将只允许转换为 int。如果您不能使用 C++20,则需要通过 std::enable_if.

将概念替换为 SFINAE