enable_if with copy constructors

时间:2017-09-25 11:14:35

标签: c++ templates sfinae enable-if

我第一次尝试std :: enable_if并且正在努力。 任何指导都将不胜感激。

作为一个玩具示例,这里是一个简单的静态向量类,我想为其定义一个复制构造函数,但行为取决于向量的相对大小:

  1. 只需将数据复制到较小或相同大小的矢量
  2. 将数据复制到一个更大的向量中,然后用零填充其余部分
  3. 所以矢量类是:

    template <size_t _Size>
    class Vector
    {
        double _data[_Size];
    
    public:
        Vector()
        {
            std::fill(_data, _data + _Size, 0.0);
        }
    
        const double* data() const
        {
            return _data;
        }
    ...
    };
    

    复制构造函数应该支持这样的东西,将v3的前2个元素复制到v2:

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

    我为行为1尝试了一个复制构造函数。像这样编译:

    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);
        }
    

    但是编译器无法将其与行为2区分开来。即使enable_if条件是互斥的。

    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);
        }
    

    我也尝试在参数中添加enable_if,但它无法推断出_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);
        }
    

    执行此操作的最佳方法是什么(使用enable_if,而不是简单的if语句)?

    由于

2 个答案:

答案 0 :(得分:7)

忽略默认值,这些构造函数的两者的签名是

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

即,它们最终是一样的。

区分它们的一种方法是使模板参数类型直接依赖于enable_if的条件:

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);
    }

顺便说一句,像_Size_OtherSize这样的名称是为实现保留的,因此对于用户代码是非法的 - 丢失下划线和/或大写字母。

另外,正如@StoryTeller暗示的那样,你不希望在_OtherSize == _Size时应用第一个构造函数,因为编译器生成的复制构造函数具有理想的行为。对于相同大小的Vector,所述构造函数已经不如复制构造函数那么专业化,因此无论如何都不会在重载决策期间选择它,但最好通过切换<=来明确目标。到<

答案 1 :(得分:4)

不要使用_Cap;它们是为实施而保留的。实际上,std源使用tgese名称​​,因为它们是保留的。不要模仿std / system header内部命名约定。

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;

你会发现这可以优化你想要的代码;在无填充的情况下,使用std::fill调用(foo, foo, 0.0),并且thr body是一个循环,当两次传递相同的指针时,编译器很容易证明这是一个noop。