隐式转换未按预期工作
implicit conversion not working as expected
我写了一个简单的 Flags
class,但是我的运算符定义遇到了问题。好像我依赖于一些不存在的隐式转换规则。
enum class SomeEnum { ONE, TWO, THREE };
template<typename Enum>
class Flags {
Flags(const Flags& f) { };
Flags(Enum e) { };
Flags& operator |=(Flags<Enum> f) {
//...;
return *this;
}
};
template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<Enum> b) { return b | a; }
template<typename Enum>
Flags<Enum> operator |(Flags<Enum> a, Enum b) { return a |= b; }
int main() {
Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
return 0;
}
编译时出现此错误:
implicit.cpp: In function ‘int main()’:
implicit.cpp:26:40: error: no match for ‘operator|’ (operand types are ‘SomeEnum’ and ‘SomeEnum’)
Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
我的理解是,SomeEnum
之一被隐式转换为 Flags<Enum>
,然后传递给正确的运算符。我缺少什么规则?
编辑:
我查看了
我确实删除了全局定义并添加了这些成员:
friend Flags operator |(Enum a, Flags b) { return b | a; }
friend Flags operator |(Flags a, Enum b) { return a |= b; }
但是错误还是一样(live demo)。
为了知道 SomeEnum
可以转换为 Flags<SomeEnum>
,它必须已经将 Enum
模板参数推导为 SomeEnum
,但它不能从参数中推断出这一点,因为它们都不匹配 Flags<Enum>
.
即必须在检查到另一种类型的转换之前进行模板参数推导。
您可以调整函数,使只有一个参数参与参数推导:
template<typename T> struct nondeduced { using type = T; }
template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<typename nondeduced<Enum>::type> b)
或等同于:
template<typename E> struct FlagType { using type = Flags<E>; }
template<typename Enum>
Flags<Enum> operator |(Enum a, typename FlagType<Enum>::type b);
这在非推导上下文中使用了 Enum
模板参数,因此只有另一个参数用于推导。
但是在这样做之后你现在有不明确的重载,因为编译器无法知道你是想转换第一个参数还是第二个参数。
您需要添加一个采用两种 SomeEnum
类型的重载,并显式执行到 Flags<SomeEnum>
的转换。如果枚举类型应该与 operator|
一起使用,那么它应该定义该运算符本身。
另一个不涉及更改枚举类型的选项是添加一个帮助程序,将枚举器转换为 Flags
对象:
template<typename Enum>
inline Flags<Enum> flags(Enum e) { return Flags<Enum>(e); }
然后你可以说SomeEnum::TWO | flags(SomeEnum::TWO)
这样就避免了歧义。
我写了一个简单的 Flags
class,但是我的运算符定义遇到了问题。好像我依赖于一些不存在的隐式转换规则。
enum class SomeEnum { ONE, TWO, THREE };
template<typename Enum>
class Flags {
Flags(const Flags& f) { };
Flags(Enum e) { };
Flags& operator |=(Flags<Enum> f) {
//...;
return *this;
}
};
template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<Enum> b) { return b | a; }
template<typename Enum>
Flags<Enum> operator |(Flags<Enum> a, Enum b) { return a |= b; }
int main() {
Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
return 0;
}
编译时出现此错误:
implicit.cpp: In function ‘int main()’:
implicit.cpp:26:40: error: no match for ‘operator|’ (operand types are ‘SomeEnum’ and ‘SomeEnum’)
Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
我的理解是,SomeEnum
之一被隐式转换为 Flags<Enum>
,然后传递给正确的运算符。我缺少什么规则?
编辑:
我查看了
我确实删除了全局定义并添加了这些成员:
friend Flags operator |(Enum a, Flags b) { return b | a; }
friend Flags operator |(Flags a, Enum b) { return a |= b; }
但是错误还是一样(live demo)。
为了知道 SomeEnum
可以转换为 Flags<SomeEnum>
,它必须已经将 Enum
模板参数推导为 SomeEnum
,但它不能从参数中推断出这一点,因为它们都不匹配 Flags<Enum>
.
即必须在检查到另一种类型的转换之前进行模板参数推导。
您可以调整函数,使只有一个参数参与参数推导:
template<typename T> struct nondeduced { using type = T; }
template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<typename nondeduced<Enum>::type> b)
或等同于:
template<typename E> struct FlagType { using type = Flags<E>; }
template<typename Enum>
Flags<Enum> operator |(Enum a, typename FlagType<Enum>::type b);
这在非推导上下文中使用了 Enum
模板参数,因此只有另一个参数用于推导。
但是在这样做之后你现在有不明确的重载,因为编译器无法知道你是想转换第一个参数还是第二个参数。
您需要添加一个采用两种 SomeEnum
类型的重载,并显式执行到 Flags<SomeEnum>
的转换。如果枚举类型应该与 operator|
一起使用,那么它应该定义该运算符本身。
另一个不涉及更改枚举类型的选项是添加一个帮助程序,将枚举器转换为 Flags
对象:
template<typename Enum>
inline Flags<Enum> flags(Enum e) { return Flags<Enum>(e); }
然后你可以说SomeEnum::TWO | flags(SomeEnum::TWO)
这样就避免了歧义。