问题
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 behaviour depends on the relative sizes of the vectors:
- just copy data into a smaller or same-sized vector
- 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 behaviour 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 behaviour 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 best way to do this (using enable_if, not a simple if statement)?
Thanks
回答1:
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 Vector
s, so it won't be selected during overload resolution anyway, but it would be best to make the intent clear by switching <=
to <
.
回答2:
Don't use _Cap
; they are reserved for the implementation. In fact, std source uses tgese names because they are reserved. Do not mimic std/system header internal naming conventions.
template <size_t O>
Vector(const Vector<O>& v) : Vector()
{
constexpr auto to_copy = (std::min)( O, Size );
constexpr auto to_fill = Size-to_copy;
auto const* src=v.data();
std::copy(src, src + to_copy, _data);
std::fill(_data + to_copy, _data + to_copy+to_fill, 0.0);
}
Vector(const Vector& v) = default;
you'll find this optimizes down to the code you want; in the no-fill case, std::fill
is called with (foo, foo, 0.0)
, and thr body is a loop that is easy to prove is a noop by a compiler when passed the same pointer twice.
来源:https://stackoverflow.com/questions/46403866/enable-if-with-copy-constructors