为何自定义tuple的get<K>()无法接收子类?如何实现兼容?
这个问题的核心在于你写的第二个get函数模板有个小错误——模板参数T没有出现在函数参数列表里,导致编译器无法推导它的值,而且函数参数的类型也没匹配你的tuple继承结构。咱们一步步来解决:
问题分析
你自定义的tuple<T, Ts...>是继承自tuple<Ts...>的,每个子类都持有第一个元素作为tail。但你的第二个get函数是这么写的:
template<size_t k, class T, class... Ts> typename std::enable_if< k != 0, typename elem_type_holder<k, tuple<T, Ts...>>::type &>::type get(tuple<Ts...> &t) { tuple<Ts...> &base = t; return get<k - 1>(base); }
这里有两个关键问题:
- 函数参数是
tuple<Ts...>&,但你用elem_type_holder查询的是tuple<T, Ts...>的类型——T这个模板参数根本没出现在函数参数里,编译器完全没法推导它是什么。 - 当你传入
Foo&(Foo继承自tuple<int,int,int>)时,模板推导不会自动通过隐式转换(子类转基类)来匹配参数类型,再加上T无法推导,自然就找不到匹配的函数了。
而std::get能工作的原因是它的函数参数直接是std::tuple<Types...>&,当你传入子类对象时,编译器可以推导Types...为基类tuple的模板参数,然后利用子类到基类的隐式引用转换完成调用。
修复方案
把第二个get函数的参数类型改成tuple<T, Ts...>&,这样T和Ts...都能从函数参数里推导出来,子类对象也能隐式转换为基类tuple<T, Ts...>的引用:
// k == 0 的情况保持不变 template<size_t k, class... Ts> typename std::enable_if< k == 0, typename elem_type_holder<0, tuple<Ts...>>::type &>::type get(tuple<Ts...> &t) { return t.tail; } // 修改后的 k != 0 的情况 template<size_t k, class T, class... Ts> typename std::enable_if< k != 0, typename elem_type_holder<k, tuple<T, Ts...>>::type &>::type get(tuple<T, Ts...> &t) { tuple<Ts...> &base = t; // 利用继承关系转成基类引用 return get<k - 1>(base); }
额外优化:支持const对象
为了让const的tuple和它的子类也能使用get,你可以再重载一组const版本的get函数:
template<size_t k, class... Ts> typename std::enable_if< k == 0, const typename elem_type_holder<0, tuple<Ts...>>::type &>::type get(const tuple<Ts...> &t) { return t.tail; } template<size_t k, class T, class... Ts> typename std::enable_if< k != 0, const typename elem_type_holder<k, tuple<T, Ts...>>::type &>::type get(const tuple<T, Ts...> &t) { const tuple<Ts...> &base = t; return get<k - 1>(base); }
现在你再测试Foo的代码:
Foo foo; std::cout << get<1>(foo) << std::endl; // 正常输出2
就可以正常工作了,不需要再手动static_cast啦。
内容的提问来源于stack exchange,提问作者user2269707
相关产品推荐
相关产品推荐

