Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enable_if with copy constructors

I am trying std::enable_if for the first time and struggling. Any guidance would be appreciated.

As a toy example, here is a simple static vector class, for which I want to define a copy constructor, but the behavior depends on the relative sizes of the vectors:

  1. just copy data into a smaller or same-sized vector
  2. copy data into a larger vector and then pad the rest with zeroes

So the vector class is:

template <size_t _Size>
class Vector
{
    double _data[_Size];

public:
    Vector()
    {
        std::fill(_data, _data + _Size, 0.0);
    }

    const double* data() const
    {
        return _data;
    }

    // ...
};

The copy constructor should support something like this: copying the first 2 elements of v3 into v2:

Vector<3> v3;
Vector<2> v2(v3);

I tried a copy constructor for behavior 1. like this, which compiles:

template <size_t _OtherSize,
typename = typename std::enable_if_t<_Size <= _OtherSize>>
Vector(const Vector<_OtherSize>& v) : Vector()
{
   std::copy(v.data(), v.data() + _Size, _data);
}

but the compiler cannot distinguish this from behavior 2. even though the enable_if conditions are mutually exclusive.

template <size_t _OtherSize,
typename = typename std::enable_if_t<_OtherSize < _Size>>
Vector(const Vector<_OtherSize>& v) : Vector()
{
    std::copy(v.data(), v.data() + _OtherSize, _data);
    std::fill(_data + _OtherSize, _data + _Size, 0.0);
}

I also tried putting enable_if in the argument instead, but it couldn't deduce the value of _OtherSize:

template <size_t _OtherSize>
    Vector(const typename std::enable_if_t<_Size <= _OtherSize, 
    Vector<_OtherSize>> & v)
    : Vector()
    {
        std::copy(v.data(), v.data() + _Size, _data);
    }

What is the way to do this (using enable_if, not a simple if statement)?

like image 548
user1683586 Avatar asked Sep 25 '17 11:09

user1683586


1 Answers

Ignoring defaults, the signature of both of those constructors is

template <size_t N, typename>
Vector(const Vector<N>&)

I.e., they're ultimately the same.

One way to differentiate them is to make the template parameter type directly dependent on enable_if's condition:

template <size_t _OtherSize,
    std::enable_if_t<(_Size <= _OtherSize), int> = 0>
    Vector(const Vector<_OtherSize>& v) : Vector()
    {
        std::copy(v.data(), v.data() + _Size, _data);
    }

template <size_t _OtherSize,
    std::enable_if_t<(_OtherSize < _Size), int> = 0>
    Vector(const Vector<_OtherSize>& v) : Vector()
    {
        std::copy(v.data(), v.data() + _OtherSize, _data);
        std::fill(_data + _OtherSize, _data + _Size, 0.0);
    }

As an aside, names like _Size and _OtherSize are reserved for the implementation and thus illegal for user code – lose the underscore and/or the capital letter.

Also, as @StoryTeller hinted at, you don't want the first constructor to apply when _OtherSize == _Size, as the compiler-generated copy constructor has ideal behavior. Said constructor is already less specialized than the copy constructor for same-sized Vectors, so it won't be selected during overload resolution anyway, but it would be best to make the intent clear by switching <= to <.

like image 150
ildjarn Avatar answered Sep 21 '22 21:09

ildjarn