代码之家  ›  专栏  ›  技术社区  ›  Die4Toast

递归调用成员箭头运算符->

  •  2
  • Die4Toast  · 技术社区  · 8 月前

    成员箭头运算符由编译器以特殊方式处理。由于重载箭头运算符可以具有任意返回类型,因此可能需要将来自不同类的多个此类运算符“链接”在一起才能获得指针值。这个“链接”过程是由编译器在幕后自动完成的。重载类方法 T operator->() { ... } 可以像任何其他方法一样手动调用(例如。 return obj.operator->(); ),但这只会产生类型的结果 T 。我想知道是否有一些特定的方法可以手动调用这个重载 operator->() 该方法将导致获取底层“链式”指针值,而不是任意结果类型 T .

    下面的例子有3种不同的方法来获取我想到的底层“链式”指针值,但似乎都没有解决这个问题:

    struct A {
      int data;
    
      int* operator->() { return std::addressof(this->data); }
    };
    
    struct B {
      A operator->() { return A(); }
    };
    
    struct C {
      B operator->() { return B(); }
    };
    
    template <typename T>
    struct D {
      T x;
    
      int* operator->() {
        // Trying to access the underlying int* pointer
        return this->x.operator->(); // (1)
        return std::addressof(*this->x); // (2)
        return this->x.operator->().operator->().operator->(); // (3)
      }
    };
    
    int main() {
      D<A> da;
      D<B> db;
      D<C> dc;
    }
    

    1. 呼叫 操作员->() T x 成员就像一种方法。对于 D<A> 专业化是可行的,但两者都 D<B> D<C> 由于调用的结果,将导致编译错误 操作员->() B C 分别不可转换为 int* .

    2. 使用解引用运算符和 std::addressof 在上面的例子中,这种方法不起作用,因为没有一个类 operator*() 定义为编译器抛出错误。即使定义了解栅栏运算符(例如迭代器类) D 类模板应该充当以下对象的代理 A , B C 类类型,因此实现 D<T>::operator->() 应明确使用依赖项 T::operator->() The T::operator*() std::addressof 组合似乎是一种“变通方法”,如果包装好,甚至可能无法正常工作 T: :操作员->() 有一些奇怪的副作用 T: :操作员*() 没有。

    3. 根据需要手动键入尽可能多的成员箭头运算符。这基本上就是编译器在幕后所做的。这种方法显然非常脆弱,只适用于 D<C> 专业化。

    对于上下文,我目前正在实现一个类型擦除的视图/范围类,该类可以从任何可以迭代的容器中构造。为了实现目的,我还为类型擦除迭代器定义了一个类,该类充当底层集合迭代器的代理。这个 struct D 在上面的示例中,表示该类型的擦除迭代器类,而 C c 成员表示特定/底层迭代器数据(例如std::vector迭代器、某种std::ranges::view迭代器等)。由于类型擦除,返回类型为 D::operator->() 不能与返回的相同 C::operator->() 因为这种类型也必须被擦除。

    1 回复  |  直到 8 月前
        1
  •  3
  •   JaMiT    8 月前

    写吧 arrow 函数模拟编译器对箭头运算符所做的操作。(你的所有尝试都没有模仿编译器所做的任意深度。)当你到达一个原始指针时,你就完成了。

    template<class T>
    T* arrow(T* ptr) {
        return ptr;
    }
    

    对于其他所有事情,调用 operator-> 然后重复。

    template<class T>
    auto* arrow(T&& ptr) {
        return arrow(ptr.operator->());
    }
    

    (顺序很重要。第一个重载需要对第二个重载可见。)

    这个 操作员-> 你的 D 然后,模板可以简单地 return arrow(x); .