C ++ 20:自动生成的运算符在派生类中是不可引用的吗? std::vector<>::iterator的比较运算符可能是实现定义的,并且很可能实现为非成员函数

问题描述

在编写与太空飞船操作员相关的C ++ 20时,我注意到了一些相当奇怪的东西。

据我了解,自C ++ 20起,比较运算符由编译器自动生成。 但是,我在自动生成运算符时遇到了一个有趣的问题。

在下面的代码中,我试图定义从MyIterator派生的vector<int>::iterator。 现在,我希望基类受到保护并显式公开函数。 因此,很自然地,我使用using声明来使用基类中的成员函数。 但是,编译器抱怨operator!=丢失了!

是否会因为自动生成的运算符“生成得太晚”而发生?

有趣的是,MyIterator2定义中显示解决方法似乎可以解决此问题。

我想听听这种奇怪行为的原因。 这是通过太空船操作员自动生成的操作员的预期行为吗? 还是这是编译器错误或未实现的功能

编译器版本信息:

[hoge@foobar]$ clang++ --version
clang version 10.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

代码(main.cpp):

#include <vector>

using MyVec = std::vector<int>;

/// Does not compile
class MyIterator : protected MyVec::iterator {
  using Base = MyVec::iterator;

  // Error
  // No member named 'operator!=' in '__gnu_cxx::__normal_iterator<int *,// std::vector<int,std::allocator<int>>>'clang(no_member)
  using Base::operator!=;
};

/// Compiles
class MyIterator2 : protected MyVec::iterator {
  using Base = MyVec::iterator;

  /// A rather ugly workaround!
  auto operator!=(const MyIterator2 &o) const {
    return static_cast<const Base &>(*this) != static_cast<const Base &>(o);
  }
};

其他说明:

GCC也以相同的方式拒绝代码

[hoge@foobar tmp]$ g++ --version
g++ (GCC) 10.2.0
copyright (C) 2020 Free Software Foundation,Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or fitness FOR A PARTIculaR PURPOSE.
[hoge@foobar tmp]$ g++ main.cpp
main.cpp:12:23: error: ‘operator!=’ has not been declared in ‘__gnu_cxx::Base’
   12 |   using Base::operator!=;
      | 

解决方法

据我了解,自C ++ 20起,比较运算符由编译器自动生成。但是,我在自动生成运算符时遇到了一个有趣的问题。

这是不正确的。比较器 operators 不会生成。比较表达式被重写。

也就是说,给出:

struct X {
    int i;
    bool operator==(X const&) const = default;
};

X{2} == X{3}直接有效,调用默认的operator==,它会进行成员比较,从而得出false

X{2} != X{3}也是有效的表达式,但它不会调用任何名为operator!=的东西。没有这样的功能。相反,它的值为!(X{2} == X{3}),得出true。尽管X{2} != X{3}是有效的表达式,但此处没有任何命名为operator!=的内容,因此您不能引用该名称的任何内容。

因此,自然地,我使用using声明来使用基类中的成员函数。但是,编译器抱怨operator!=丢失了!

在C ++ 20中,我们几乎几乎不需要使用operator!=,因此几乎所有它们都已从标准库规范中删除,并且标准库很可能经过了#ifdef编辑。出来。更少的代码需要解析。由于运算符的重写,不等式表达式仍然有效,但是不再有名为operator!=的东西。

所以您不能using

但是不再需要标准库operator!=的相同原因也适用于您-您也不需要它。


请进一步注意,using Base::operator!=;甚至不能在C ++ 17中工作,因为没有operator!=被编写为成员函数的义务。可以将其编写为自由函数,然后即使有一个名为operator!=的函数也无法正常工作。

,

std::vector<>::iterator的比较运算符可能是(实现定义的),并且很可能实现为非成员函数

因此,自然地,我使用using声明来使用基类中的成员函数。 [...]

iterator类型的std::vector不需要将其比较运算符实现为成员函数;按照[vector.overview]/3

类型iteratorconst_­iterator符合constexpr迭代器要求([iterator.requirements.general])。

[...]

using iterator = implementation-defined; // see [container.requirements]

相反,我们可能会注意到,受[predef.iterators]约束的所有迭代器适配器将其比较运算符指定为非成员函数。

因此,GCC和Clang都正确拒绝了您的程序,并且还提供了准确的错误消息,说明他们拒绝程序的原因。与以下简化示例进行比较:

struct A {
    int x;
    // Non-member operator==.
    friend bool operator==(const A&,const A&) = default;
};

struct B {
    int x;
    // Member operator==.
    bool operator==(const B&) const = default;
};

struct ADerived : public A {
    using A::operator==;  // Error: no MEMBER named 'operator==' in 'A'
};

struct BDerived : public B {
    using B::operator==;  // OK.
};
,

迭代器,包括std :: vector迭代器,不必是类。

特别是,将std vector的迭代器作为原始指针是完全合法的。

从指针继承将不会顺利结束。

因此,使用您的“继承和转发”技术并不安全。正如您所了解的那样,它在std库版本中也不稳定;表达式it1 != it2可以保证工作,但是如果库选择了,即使矢量迭代器将指针放在其中,它也可以使用非成员!=


我可能建议的一个技巧是编写一个“索引器”。索引器采用类型T并对其进行迭代。我既可以用它来编写迭代器迭代器,也可以用它来写整数迭代器。

template<class T,bool forward_iterator_traits=false>
struct indexing_iterator {
  T value;
  T const& operator*() const { return value; }
  indexing_iterator& operator++()
  {
    ++value;
    return *this;
  }
  indexing_iterator operator++(int)
  {
    indexing_iterator tmp(*this); // copy
    ++*this;
    return tmp;   // return old value
  }
  // etc
};

等添加一些迭代器特征转发支持(通过部分专门化iterator_traits<indexing_iterator<T,true>>:iterator_traits<T>

现在,可以使用indexing_iterator<It>声明从using安全地继承。您必须自己写operator*

您可以编写for(auto it : iterators_of(Container))for ( auto i : indexes_of(Container))循环。

双赢。