如何让编译器正确区分foo模板类的分量乘法与标量乘法?
类容器模板类
foo的乘法运算符重载问题解析 问题场景
实现了支持分量乘法(componentwise multiplication)和标量乘法(scalar multiplication)的模板类foo,但当使用foo<int>对象a与可隐式转换为foo<int>的bar对象b执行a * b时,编译器无法自动匹配分量乘法,必须显式转换b为foo<int>才能正常运行。需要明确编译器的重载决议逻辑,以及是否必须显式转换。
代码实现
#include <algorithm> #include <vector> template<typename T> class foo { public: foo() = default; template<typename U> requires std::is_convertible_v<U, T> foo(foo<U> const& other) { std::copy(other.begin(), other.end(), begin()); } template<typename U> requires std::is_convertible_v<U, T> foo& operator*=(foo<U> const& rhs) { std::transform(begin(), end(), rhs.begin(), begin(), [](T const& lhs, U const& rhs) { return lhs * rhs;}); return *this; } template<typename U> requires std::is_convertible_v<U, T> foo& operator*=(U const& rhs) { std::transform(begin(), end(), begin(), [&rhs](T const& lhs) { return lhs * rhs; }); return *this; } template<typename U> auto operator*(foo<U> const& rhs) const { foo<decltype(T{} * U{})> result = *this; return result *= rhs; } template<typename U> auto operator*(U const& rhs) const { foo<decltype(T{} * U{})> result = *this; return result *= rhs; } constexpr auto begin() noexcept { return m_x.begin(); } constexpr auto begin() const noexcept { return m_x.begin(); } constexpr auto end() noexcept { return m_x.end(); } constexpr auto end() const noexcept { return m_x.end(); } private: std::vector<T> m_x; }; struct bar { operator foo<int>() const { return foo<int>{}; } }; int main() { foo<int> a; bar b; auto const c = a * b; // 编译失败,显式转换static_cast<foo<int>>(b)则正常 }
问题原因
编译器的重载决议遵循匹配优先级规则:
- 对于
a * b,有两个候选的operator*模板:- 分量乘法版本:
operator*(foo<U> const&)—— 需要将bar通过用户定义的转换(bar::operator foo<int>())转换为foo<int>,属于用户定义转换,匹配优先级较低。 - 标量乘法版本:
operator*(U const&)—— 模板参数U可直接推导为bar,不需要任何转换,匹配优先级更高。
- 分量乘法版本:
- 虽然标量乘法版本的
operator*在实例化时会因operator*=(bar const&)不满足std::is_convertible_v<bar, int>的约束而失败,但编译器会优先尝试优先级更高的候选,导致最终编译错误。
解决方案
不需要显式转换,只需给标量乘法的operator*添加约束,确保它仅在U符合标量乘法的要求时才参与重载决议。比如限制U可转换为T:
修改标量乘法的operator*模板:
template<typename U> requires std::is_convertible_v<U, T> auto operator*(U const& rhs) const { foo<decltype(T{} * U{})> result = *this; return result *= rhs; }
添加约束后,当U是bar时,std::is_convertible_v<bar, int>为false,该模板会被SFINAE排除,编译器会自动选择分量乘法版本,此时bar会隐式转换为foo<int>,正常执行分量乘法。
补充说明
如果希望更严格地区分量量和标量,可以将标量版本的约束改为std::is_arithmetic_v<U>(仅允许算术类型作为标量),这样能进一步避免非算术类型的错误匹配,提升代码的健壮性。
内容的提问来源于stack exchange,提问作者0xbadf00d
相关产品推荐
相关产品推荐

