自定义转基础类型是否可删除?
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() const
从 operator Integral() const
实例化的,因为从 int
到 int
的转换优于 bool
到 int
。 (或者至少这似乎是意图,参见 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)
,从 int
或 bool
到 long
的转换都不比另一个好。因此,出于重载决议的目的,隐式转换序列将成为 不明确的转换序列 。此转换序列被认为不同于所有其他 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
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() const
从 operator Integral() const
实例化的,因为从 int
到 int
的转换优于 bool
到 int
。 (或者至少这似乎是意图,参见 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)
,从 int
或 bool
到 long
的转换都不比另一个好。因此,出于重载决议的目的,隐式转换序列将成为 不明确的转换序列 。此转换序列被认为不同于所有其他 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
.