【问题标题】:enable_if with copy constructorsenable_if 与复制构造函数
【发布时间】:2017-09-25 11:14:34
【问题描述】:

我第一次尝试std::enable_if 并且正在苦苦挣扎。任何指导将不胜感激。

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

  1. 只需将数据复制到更小或相同大小的向量中
  2. 将数据复制到更大的向量中,然后用零填充其余部分

所以vector 类是:

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 语句)?

【问题讨论】:

  • 根据定义,复制构造函数不能是模板。您可以拥有一个复制的模板化构造函数,但它仍然不是复制构造函数。 ;-]
  • 这不是问题,但是以下划线开头的名称后跟大写字母(_Size_OtherSize)和包含两个连续下划线的名称保留供实现使用.不要在你的代码中使用它们。
  • 不相关,但在std::enable_if_t 之前不需要typename

标签: c++ templates sfinae enable-if


【解决方案1】:

忽略默认值,两个的构造函数的签名是

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 时应用第一个构造函数,因为编译器生成的复制构造函数具有理想的行为。对于相同大小的Vectors,所述构造函数已经不像复制构造函数那样专门化,因此在重载决议期间无论如何都不会选择它,但最好通过将&lt;= 切换到&lt; 来明确意图.

【讨论】:

  • 看你的评论,&lt;= 有问题,应该是&lt;。如果只是为了防止在调用“错误”的 c'tor 时造成混淆。
  • @StoryTeller :你的意思是构造函数不适用而不是编译器生成的copy-c'tor?是的,可能,但我试图将答案限制在代码的语言问题而不是逻辑问题(OP 没有询问)。好点子,我会记下它——谢谢!
  • 毕竟它永远不会在重载决议期间应用。没有歧义的风险,除了 OP 想知道为什么他们的断点没有被命中:)
  • 下划线(例如大小等之前)的禁止来自哪里?我搜索了许多书籍和网站,但找不到任何提及它的内容。大概 Size_ 是允许的?我的目标是看起来像一个类名,但有细微的不同。
  • @user1683586 : C++14 [reserved.names]/2: "如果程序在保留名称的上下文中声明或定义名称,除非明确允许子句,其行为未定义。" 和 [global.names]/1.1: "每个包含双下划线 __ 或以下划线后跟大写字母的名称都保留给实现任何用途。” 其他版本标准的措辞类似。 Size_ 确实不错,虽然对我来说有点奇怪。 ¯\_(ツ)_/¯
【解决方案2】:

不要使用_Cap 之类的名称;它们是为实施而保留的。事实上,std source 使用这些名称​​因为它们是保留的。不要模仿 std/system 标头内部命名约定。

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) 一起调用,并且主体是一个循环,当传递相同的指针两次时,编译器很容易证明它是空操作。

【讨论】:

  • 你写(std::min)所以它被解析为一个表达式,std::min被视为一个不合格的ID?如果是,为什么?
  • @YSC :避免 windows.h 宏恶作剧是一种猜测。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 2013-10-13
  • 2018-03-12
  • 2011-05-24
相关资源
最近更新 更多