【问题标题】:invalid convertion in std::accumulatestd::accumulate 中的无效转换
【发布时间】:2015-03-04 04:19:19
【问题描述】:

我有一个类代表N 维度中的一个点,带有一个min 静态函数(逐个字段的最小值)

template<typename T, std::size_t N>
class Point : public std::array<T,N>
{
public:
    template<typename... Args>
    Point(Args&&... args) : std::array<T,N>{{args...}} {}

    // ...

    static Point min(const Point&, const Point&) {
        // ...  
    }
};

我写的时候一切都很好

Point<float,3> a = {0.f, 1.f, 2.f};
Point<float,3> b = {2.f, 1.f, 0.f};
Point<float,3> c = Point<float,3>::min(a,b); // OK

但如果我尝试在数组上使用std::accumulate

Point<float,3> array[100] = ... ;
Point<float,3> min = std::accumulate(array, array+100, array[0], Point<float,3>::min); // Error

我收到一个错误:

error: cannot convert ‘Point<float, 3ul>’ to ‘float’ in initialization
adimx::Point<T,N>::Point(Args&&... args) : std::array<T,N>{{args...}}

这是std::accumulate 实现与我的构造函数不兼容的问题吗?

【问题讨论】:

  • 可变参数构造函数正在劫持复制构造函数调用。约束它。
  • @T.C.这很有意义。如何约束它?
  • @Amxx 隐式指定复制构造函数:Point(const Point &amp;) = default;
  • T.C.说是对的。转发引用为复制ctor调用产生了更好的匹配,因此它被重载决议选择。仍然为此类生成 Copy-ctor。它不必显式默认(用户声明)
  • 你在重复计算array[0]。如果您使用array[0] 作为初始值,则从array+1 开始累积。或者,使用Point&lt;float,3&gt;(0,0,0) 作为初始值。

标签: c++ c++11 accumulate stdarray


【解决方案1】:

这是约束该构造函数的一种方法,以便它仅在所有参数都可以隐式转换为float 时才参与重载决议:

template<bool... > class bool_pack;
template<bool... b>
using all_true = std::is_same<bool_pack<true, b...>, bool_pack<b..., true>>;

template<typename... Args,
         class = typename std::enable_if<all_true<std::is_convertible<Args, float>::value...>::value>::type>
Point(Args&&... args) : std::array<T,N>{{args...}} {}

【讨论】:

    【解决方案2】:

    由于构造函数通过重载决议获胜,您可以提供所需构造函数的默认版本:

    Point(Point&) = default;
    Point(const Point&) = default;
    Point(Point&&) = default;
    Point& operator=(const Point&) = default;
    

    【讨论】:

    • 对不起,你错了。转发引用会更好地匹配 copy-ctor 调用,因此它会被重载决议选中。仍然为此类生成 Copy-ctor。它不必显式默认(用户声明)
    【解决方案3】:

    新解决方案:

    template <bool J>
    using disable_if=enable_if<!J>;
    
    template <typename T,typename... Tail> struct getHead
    {
        typedef typename decay<T>::type type;
    };
    
    template<typename... Args,typename = typename disable_if<is_same<typename getHead<Args...>::type,Point<T,N> >::value >::type >
        Point(Args&&... args) : std::array<T,N> {{args...}}
        {
            //...
        }
    

    我相信这个解决方案是完美的。只有当参数是Point本身时,我们才停止转发引用变量参数构造函数。任何其他类型仍然调用转发引用变量参数构造函数。 无论Point&lt;float,3&gt;Point&lt;int,3&gt;Point&lt;int,2&gt;Point&lt;user-define,numx&gt;它总是可以的。


    至少有三种解决方案供您选择。

    首先,为了避免转发引用变量参数构造函数劫持了复制构造函数,所以去掉了这个函数,而不是

    template<typename... Args>
            Point(Args... args) : std::array<T,N> {{args...}} {}//remove &&
    

    这个解决方案是避免问题而不是解决问题。

    第二,正如TC所说,停止转发引用变量参数构造函数劫持构造函数,只要当所有类型都适合时启用。这种方式比较复杂,也削弱了你模板的应用范围。

    第三,正如MSalters所说,将array[0]改为Point&lt;float,3&gt;(0,0,0)

    Point<float,3> min = std::accumulate(array, array+100, Point<float,3>(0,0,0), Point<float,3>::min);
    

    没关系,但为什么呢?

    正如 n4260 12.9 31.3 C++ Standrad 所说:

    当一个临时类对象没有绑定到一个引用 (12.2)将被复制/移动到具有相同的类对象 cv-unqualified 类型,复制/移动操作可以省略 将临时对象直接构造到 省略复制/移动

    所以accumulation直接用三个float调用Point构造函数,不调用复制构造函数。所以不调用转发引用变量参数构造函数传递一个Point Object作为参数,这就是你的编译错误。

    缺点是每次使用accumulate函数都需要传入右值,不能是左值。

    【讨论】:

      猜你喜欢
      • 2023-03-06
      • 1970-01-01
      • 2020-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多