V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tony042
V2EX  ›  C++

C++友元函数问题

  •  
  •   Tony042 · 2020-08-19 05:13:53 +08:00 · 2422 次点击
    这是一个创建于 1549 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先上代码,注意 get 函数和其友元函数

    #include <utility>
    #include <type_traits>
    
    template <unsigned Height, typename T, bool = std::is_class_v<T> && !std::is_final_v<T>>
    class TupleElt;
    
    template <unsigned Height, typename T>
    class TupleElt<Height, T, false>
    {
    private:
        T value;
    
    public:
        TupleElt() = default;
        template <typename U>
        TupleElt(U &&other) : value(std::forward<U>(other)) {}
        T &get() { return value; }
        T const &get() const { return value; }
    };
    
    template <unsigned Height, typename T>
    class TupleElt<Height, T, true> : private T
    {
    public:
        TupleElt() = default;
        template <typename U>
        TupleElt(U &&other) : T(std::forward<U>(other)) {}
        T &get() { return *this; }
        T const &get() const { return *this; }
    };
    
    template <unsigned H, typename T>
    T &getHeight(TupleElt<H, T> &te)
    {
        return te.get();
    }
    
    template <typename... Types>
    class Tuple;
    
    template <unsigned I, typename... Elements>
    auto get(Tuple<Elements...> &t) -> decltype(getHeight<sizeof...(Elements) - I - 1>(t))
    {
        return getHeight<sizeof...(Elements) - I - 1>(t);
    }
    
    
    template <typename... Types>
    class Tuple;
    
    template <typename Head, typename... Tail>
    class Tuple<Head, Tail...> : private TupleElt<sizeof...(Tail), Head>, private Tuple<Tail...>
    {
        template <unsigned I, typename... Elements>
        friend auto get(Tuple<Elements...> &t) -> decltype(getHeight<sizeof...(Elements) - I - 1>(t));  \\ 友元函数声明
    
    private:
        using HeadElt = TupleElt<sizeof...(Tail), Head>;
    
    public:
        Head &getHead() { return static_cast<HeadElt *>(this)->get(); }
        Head const &getHead() const { return static_cast<HeadElt const *>(this)->get(); }
        Tuple<Tail...> &getTail() { return *this; }
        Tuple<Tail...> const &getTail() const { return *this; }
    };
    
    template <>
    class Tuple<>
    {
    };
    
    int main()
    {
        Tuple<char, char> t1;
        get<0>(t1);
        return 0
    }
    

    我跟着 C++ templates 2nd edition, 实现了一个简单的 tuple 类和 get 函数,其中 tuple 类里面的元素被 tuple 类私有继承,get 函数通过计算元素高度,来实现直接从派生类到基类的转换来访问对应的元素,由于 tupleElt 类被私有继承,get 函数必须在 tuple 类中被声明为友元函数才可以被访问(上述代码注释处)但是即使被声明为了友元函数,gcc 还是提示 get 函数无法访问 tuple 的基类,导致编译错误,但 MSVC 可以顺利编译通过,是为什么呢,如果有错误该怎么改?

    10 条回复    2020-08-20 10:55:09 +08:00
    Tony042
        1
    Tony042  
    OP
       2020-08-19 05:36:49 +08:00
    刚才又试了下,clang-10, MSVC 16.7.0 都可以编译通过,gcc-9, gcc-10 都编译不通过...
    Tony042
        2
    Tony042  
    OP
       2020-08-19 10:01:33 +08:00
    果然 C++的回答就是少啊,自顶一下,另外题中代码可以通过这个链接调试 https://godbolt.org/z/Wr4arz
    codehz
        3
    codehz  
       2020-08-19 10:34:44 +08:00   ❤️ 1
    虽然不知道发生了什么,但是你改成这样就能通过编译 https://godbolt.org/z/bq8sEn
    另外你这个方法做 tuple 居然还需要 static_cast,这肯定和正确的做法有所偏离。。。
    Tony042
        4
    Tony042  
    OP
       2020-08-19 10:45:07 +08:00
    @codehz 同不知道发生了什么,友元函数不行,友元类却可以,gcc 这行为挺奇怪的。stl 里面的 get 函数就是通过 static_cast 获得 tuple 里元素的值。《 C++ templates 》这本书代码有点坑,好多地方都有小错误,或者不太稳定
    Tony042
        5
    Tony042  
    OP
       2020-08-19 10:49:04 +08:00
    @codehz anyway 谢谢老哥的回复啊,基本上我的每个问题都是你帮我回答的,谢谢啦
    Wirbelwind
        6
    Wirbelwind  
       2020-08-19 11:18:37 +08:00
    大概是编译器实现问题?

    使用 private 类型 继承的类型,就算是被继承类型中的 public 成员也是不能访问的。

    写个简化版的 demo 的话,clang 和 msvc 也是不给过的。

    简单的解决办法就是 private TupleElt 改成 public TupleElt
    Tony042
        7
    Tony042  
    OP
       2020-08-19 11:27:24 +08:00
    @Wirbelwind 应该是编译器实现问题吧,我查了下 stl 源码,msvc 的方式是将 get 函数声明为友元函数,gcc 是声明友元类。 在这个 case 里面,由于每个 tuple 类的实现都声明了所有 get 函数为友元函数,其实按理说 private 不会影响 get 函数做 Derived->Base 转换的
    constexpr
        8
    constexpr  
       2020-08-19 18:38:21 +08:00   ❤️ 1
    我研究了一下,我觉得这个问题大概跟友元有关,为此我写了一段极短的代码

    struct Base { void f() {} };

    void m(Base*) {}

    template<typename T>auto v2ex(T* b) -> decltype(m(b));

    struct Derived : private Base {
    template<typename T> friend auto v2ex(T* d) -> decltype(m(d)); //problem HERE!!!
    };

    template<typename T> auto v2ex(T* d) -> decltype(m(d)) { d->f(); }

    int main() {
    Derived d;
    v2ex(&d);
    }

    这段代码能在 MSVC, clang 中编译通过并运行, g++无法编译.

    这段代码的问题在于, 模板函数 V2EX 是类 Derived 的友元, 故可以在 V2EX 中自由转换成类 Base. 但是 V2EX 的声明有这么一段 "decltype(m(b))" , 问题是函数 m 是否应该是类 Derived 的友元呢? 如果是的话, 我传给他一个类 Dervied 的指针,他应该在 V2EX 中能畅通无阻的转换成类 Base, clang 和 MSVC 都认为是的,所以编译通过, 而 g++认为 m 不是 Derived 的友元, m 中不允许 Derived->Base 的转换, 这就是他无法编译的原因.
    对应你的代码就是 g++不认为 getHeight 是 Tuple 的友元, 所以在友元声明中 "getHeight<sizeof...(Elements) - I - 1>(t)", g++不允许 Tuple 类型的参数 t 向基类 TupleElt 的转换!!!

    有几点说明一下:
    ■ 代码也许可以写的更短, 但是为了恰好在 MSVC 和 clang 下编译成功, 而 gcc 不成功才写的稍复杂了一点.
    ■ 你的代码比我这段代码复杂的多, 而且涉及递归继承, 多重继承. 虽然我不敢肯定我提到的这个问题就一定是你问题的解答, 但从 g++编译器给出的错误提示中, 大致就是我提出的这个问题.
    ■ 至于 ISO 标准是怎么样的, 我不清楚. 不过也能看出不同编译器对标准的解读,实现都有偏差.
    constexpr
        9
    constexpr  
       2020-08-19 18:42:12 +08:00
    @constexpr V2EX 好像自动变大写了!~
    Wirbelwind
        10
    Wirbelwind  
       2020-08-20 10:55:09 +08:00
    @constexpr 这样理解确实不错,之前我试了在 base class 加友元函数,但是出现 ambigous 问题 就作罢了

    但是编译器报的错误是 inaccessible base of TupleElt,也可能是 gcc 不允许访问 private

    友元一定程度上破坏了 oop 特性
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1817 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 16:45 · PVG 00:45 · LAX 08:45 · JFK 11:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.