Search code examples
c++c++11accumulatestdarray

invalid convertion in std::accumulate


I have a class representing a point in N dimensions with a min static function (minimum field by field)

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&) {
        // ...  
    }
};

Everything works well when I write

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

But if I try to use std::accumulate over an array

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

I get an error :

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

Is this an issue with std::accumulate implementation not being compatible with my constructor ?


Solution

  • New solution:

    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...}}
        {
            //...
        }
    

    I believe this solution is perfect.We stop forwarding reference variable parameter constructor only when parameter is Point itself.Any other type still call forwarding reference variable parameter constructor. No matter Point<float,3> or Point<int,3> or Point<int,2> or Point<user-define,numx>it always OK.


    There are at least three solutions for you to choose.

    First, in order to avoid forwarding reference variable parameter constructor hijacked the copy constructor, so removal of this function, instead of

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

    this solution is to avoid problems instead of solving the problem.

    Second, as TC said, stop forwarding reference variable parameter constructor hijacked constructor, as long as when all types all fit when enabled. This way is more complex, but also weaken the application scope of your template.

    Third, as MSalters said, change array[0] to Point<float,3>(0,0,0),

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

    It is OK,but why?

    As n4260 12.9 31.3 C++ Standrad says:

    when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

    So accumulate directly call Point constructor with three float ,doesn't call copy constructor.So doesn't call forwarding reference variable parameter constructor pass a Point Object as a parameter,this is where your compile error.

    Drawback is that every time you use the accumulate function require incoming rvalues, and cannot be an lvalue.