先上代码,注意 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 可以顺利编译通过,是为什么呢,如果有错误该怎么改?
1
Tony042 OP 刚才又试了下,clang-10, MSVC 16.7.0 都可以编译通过,gcc-9, gcc-10 都编译不通过...
|
2
Tony042 OP 果然 C++的回答就是少啊,自顶一下,另外题中代码可以通过这个链接调试 https://godbolt.org/z/Wr4arz
|
3
codehz 2020-08-19 10:34:44 +08:00 1
虽然不知道发生了什么,但是你改成这样就能通过编译 https://godbolt.org/z/bq8sEn
另外你这个方法做 tuple 居然还需要 static_cast,这肯定和正确的做法有所偏离。。。 |
4
Tony042 OP @codehz 同不知道发生了什么,友元函数不行,友元类却可以,gcc 这行为挺奇怪的。stl 里面的 get 函数就是通过 static_cast 获得 tuple 里元素的值。《 C++ templates 》这本书代码有点坑,好多地方都有小错误,或者不太稳定
|
6
Wirbelwind 2020-08-19 11:18:37 +08:00
大概是编译器实现问题?
使用 private 类型 继承的类型,就算是被继承类型中的 public 成员也是不能访问的。 写个简化版的 demo 的话,clang 和 msvc 也是不给过的。 简单的解决办法就是 private TupleElt 改成 public TupleElt |
7
Tony042 OP @Wirbelwind 应该是编译器实现问题吧,我查了下 stl 源码,msvc 的方式是将 get 函数声明为友元函数,gcc 是声明友元类。 在这个 case 里面,由于每个 tuple 类的实现都声明了所有 get 函数为友元函数,其实按理说 private 不会影响 get 函数做 Derived->Base 转换的
|
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 标准是怎么样的, 我不清楚. 不过也能看出不同编译器对标准的解读,实现都有偏差. |
10
Wirbelwind 2020-08-20 10:55:09 +08:00
@constexpr 这样理解确实不错,之前我试了在 base class 加友元函数,但是出现 ambigous 问题 就作罢了
但是编译器报的错误是 inaccessible base of TupleElt,也可能是 gcc 不允许访问 private 友元一定程度上破坏了 oop 特性 |