【问题标题】:ADL warning: ambiguous conversion with boost operators and SFINAEADL 警告:使用 boost 运算符和 SFINAE 进行模棱两可的转换
【发布时间】:2023-03-13 08:51:01
【问题描述】:

我试图了解以下代码在 ADL 期间出现的模棱两可的转换警告:

#include <boost/operators.hpp>
#include <boost/polygon/polygon.hpp>

class Scalar
    : private boost::multiplicative< Scalar, double > {
  public:
    explicit Scalar( double val ) : mVal( val ) {}
    Scalar &operator*=(double rhs) noexcept {
        mVal *= rhs;
        return (*this);
    }

    Scalar &operator/=(double rhs) noexcept {
        mVal /= rhs;
        return (*this);
    }
  private:
    double mVal;
};

using Coordinate = int;
using Polygon = boost::polygon::polygon_with_holes_data<Coordinate>;
using Point = boost::polygon::polygon_traits<Polygon>::point_type;

template <class T, typename = std::enable_if_t<std::is_arithmetic_v<std::remove_reference_t<T>>>>
Point operator*(const Point &a, T b) noexcept {
   return Point(a.x() * b, a.y() * b);
}

int main(int argc, char *argv[]){
    Scalar a( 10 );
    int b = 10;
    Scalar a_times_b = a * b;
    return 0;
}

我收到以下 GCC 11.2 警告:

<source>: In function 'int main(int, char**)':
<source>:33:28: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
   33 |     Scalar a_times_b = a * b;
      |                            ^
In file included from <source>:1:
/opt/compiler-explorer/libs/boost_1_78_0/boost/operators.hpp:268:1: note: candidate 1: 'Scalar boost::operators_impl::operator*(const Scalar&, const double&)'
  268 | BOOST_BINARY_OPERATOR_COMMUTATIVE( multipliable, * )
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:26:7: note: candidate 2: 'Point operator*(const Point&, T) [with T = int; <template-parameter-1-2> = void; Point = boost::polygon::point_data<int>]'
   26 | Point operator*(const Point &a, T b) noexcept {
      |       ^~~~~~~~
<source>:33:12: warning: variable 'a_times_b' set but not used [-Wunused-but-set-variable]
   33 |     Scalar a_times_b = a * b;
      | 

https://godbolt.org/z/qzfvjr86c。解决此问题的一种方法是也继承 boost::multiplicative&lt; Scalar, int &gt; 并可能还为 int 定义运算符 *= 和 /=(这在技术上是不必要的,因为我们得到了从 intdouble 的隐式转换)。

我的困惑:

对于所谓的“第一”,有一个隐式的内置int-&gt;double 转换。对于所谓的“第二”,编译器是否在谈论从 Scalar 类到 Point 的一些转换?我不确定这个转换链是什么样子的,因为我没有定义任何将 Scalar 类转换为 Point 的方法。如果启用,我有什么遗漏吗?这是 Boost 或 GCC 中的某种错误吗?

【问题讨论】:

  • “启用 if 是否缺少某些东西” - 我想:它太开放并导致“同样糟糕”的重载匹配通过隐式转换
  • @sehe 我不确定为什么它“同样糟糕”。例如,如果我去掉 SFINAE 并只定义 operator*(const Point &amp;a, int b),即使无法将 Scalar 转换为 Point,我仍然会收到模棱两可的转换警告。
  • 我也不确定,但我确定编译器会竭尽全力通过写一篇关于它的错误小说来准确地向你解释这一点。除非我绝对必须阅读,否则我实际上并没有阅读这些内容的习惯。相反,我避免过度开放的重载并使用 ADL/ADL 障碍来进行选择性。这意味着在全局命名空间中没有运算符,并且很少在作用域内使用 using (除非我愿意冒险在将来出现新的重载时代码会中断)
  • 如果我删除继承并将运算符定义为友元函数,我会收到相同的警告。猜猜这是编译器的错......
  • 从来都不是*。这是关于Point 可构造性的一些假设:no problem vs using Boost Polygon(*适用某些条件)。同样,您的问题是全局命名空间重载过于开放。

标签: c++ gcc boost operators


【解决方案1】:

您在全局命名空间中的免费operator* 太开放了。它主动假设Point 不能从Scalar 构造,例如:

你应该更多地限制它:

template <
    typename P, typename S,
    typename IsPoint = typename boost::polygon::is_point_concept<
        typename boost::polygon::geometry_concept<P>::type>::type,
    typename = std::enable_if_t<IsPoint::value and std::is_arithmetic_v<S>>>
auto operator*(P const& a, S const& b) noexcept {
    return boost::polygon::construct<Point>(a.x() * b, a.y() * b);
}

我也建议你

  • 如图所示,使用点特征来构造点,而不是假设直接构造
  • 不要将运算符模板放在全局命名空间中(未显示)
  • 使用已经存在并且可能具有更多安全性和/或优化的boost::polygon::scale(未显示)

现场演示

Live On Compiler Explorer

#include <type_traits>

struct Scalar {
    explicit Scalar(double val) : mVal(val) {}

private:
    friend Scalar operator*(Scalar lhs, double rhs) noexcept {
        lhs.mVal *= rhs;
        return lhs;
    }

    double mVal;
};

#include <boost/polygon/polygon.hpp>
using Coordinate = int;
using Polygon    = boost::polygon::polygon_with_holes_data<Coordinate>;
using Point      = boost::polygon::polygon_traits<Polygon>::point_type;

template <
    typename P, typename S,
    typename IsPoint = typename boost::polygon::is_point_concept<
        typename boost::polygon::geometry_concept<P>::type>::type,
    typename = std::enable_if_t<IsPoint::value and std::is_arithmetic_v<S>>>
auto operator*(P const& a, S const& b) noexcept {
    return boost::polygon::construct<Point>(a.x() * b, a.y() * b);
}

static_assert(std::is_arithmetic_v<double>);
static_assert(not std::is_arithmetic_v<Scalar>);

int main()
{
    auto s = Scalar(10) * 10;
    auto p = Point(10, 20) * 42;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 2010-12-10
    相关资源
    最近更新 更多