C++友元函数问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tony042
V2EX    C++

C++友元函数问题

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

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

    #include <utility> #include <type_traits> template <unsigned Height, typename T, bool = std::is_class_v<T> && !std::is_final_v<T>> class TupleElt; teplate <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     Solana     2505 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 06:04 PVG 14:04 LAX 22:04 JFK 01:04
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86