如何让继承std::tuple的Identifier类支持结构化绑定?
让自定义tuple子类支持结构化绑定的解决方法
我定义了一个继承自std::tuple的模板类Identifier,代码如下:
#include <concepts> #include <cstdint> #include <iostream> #include <string> #include <tuple> #include <vector> template< typename ... Args > class Identifier: public std::tuple< Args... > { public: Identifier( Args... args ) requires( sizeof...( Args ) != 0 ) : std::tuple< Args... >( std::move( args )... ) {} Identifier( std::tuple< Args... > tuple ):std::tuple< Args... >( tuple ) {} Identifier() {} Identifier(std::convertible_to<std::tuple<Args...>> auto&& identifier) : std::tuple<Args...>(std::forward<decltype(identifier)>(identifier)) {} }; int main() { Identifier<std::wstring, std::int64_t> id(L"id", 10); auto& [ str_id, num_id ] = id; std::tuple< std::wstring, int64_t > tuple_id(L"id", 10); auto& [ str_t_id, num_t_id ] = tuple_id; return 0; }
对Identifier对象使用结构化绑定语句auto& [ str_id, num_id ] = id;时,报错:
Type 'Identifier<std::wstring, std::int64_t>' (aka 'Identifier<basic_string<wchar_t>, long long>') decomposes into 1 element, but 2 names were provided
但直接对std::tuple类型的变量使用相同语句却能正常运行,请问如何修改让Identifier支持结构化绑定?
解决方法
结构化绑定不会自动继承基类std::tuple的分解逻辑,必须显式为Identifier提供tuple-like类型的支持,以下是两种可行方案:
方案一:特化标准库的tuple相关模板并提供全局get函数
在类定义外部,特化std::tuple_size、std::tuple_element,并实现全局的get函数,将调用转发到基类:
#include <concepts> #include <cstdint> #include <iostream> #include <string> #include <tuple> #include <vector> template< typename ... Args > class Identifier: public std::tuple< Args... > { public: Identifier( Args... args ) requires( sizeof...( Args ) != 0 ) : std::tuple< Args... >( std::move( args )... ) {} Identifier( std::tuple< Args... > tuple ):std::tuple< Args... >( tuple ) {} Identifier() {} Identifier(std::convertible_to<std::tuple<Args...>> auto&& identifier) : std::tuple<Args...>(std::forward<decltype(identifier)>(identifier)) {} }; // 特化tuple_size,告诉编译器Identifier的元素数量 template<typename... Args> struct std::tuple_size<Identifier<Args...>> : std::tuple_size<std::tuple<Args...>> {}; // 特化tuple_element,指定每个索引对应的元素类型 template<std::size_t I, typename... Args> struct std::tuple_element<I, Identifier<Args...>> : std::tuple_element<I, std::tuple<Args...>> {}; // 实现全局get函数,转发到基类的get template<std::size_t I, typename... Args> constexpr auto& get(Identifier<Args...>& id) noexcept { return std::get<I>(static_cast<std::tuple<Args...>&>(id)); } template<std::size_t I, typename... Args> constexpr const auto& get(const Identifier<Args...>& id) noexcept { return std::get<I>(static_cast<const std::tuple<Args...>&>(id)); } template<std::size_t I, typename... Args> constexpr auto&& get(Identifier<Args...>&& id) noexcept { return std::get<I>(static_cast<std::tuple<Args...>&&>(id)); } int main() { Identifier<std::wstring, std::int64_t> id(L"id", 10); auto& [ str_id, num_id ] = id; // 现在可以正常工作 std::tuple< std::wstring, int64_t > tuple_id(L"id", 10); auto& [ str_t_id, num_t_id ] = tuple_id; return 0; }
方案二:在类内定义tuple相关别名和成员get函数(C++20+)
如果使用C++20及以上版本,可以在Identifier类内部定义tuple_size的别名和成员版get函数,让编译器自动识别:
#include <concepts> #include <cstdint> #include <iostream> #include <string> #include <tuple> #include <vector> template< typename ... Args > class Identifier: public std::tuple< Args... > { public: // 继承tuple的大小 static constexpr std::size_t size() noexcept { return sizeof...(Args); } using std::tuple_size<std::tuple<Args...>>::value; // 定义每个索引的元素类型别名 template<std::size_t I> using tuple_element_t = typename std::tuple_element<I, std::tuple<Args...>>::type; Identifier( Args... args ) requires( sizeof...( Args ) != 0 ) : std::tuple< Args... >( std::move( args )... ) {} Identifier( std::tuple< Args... > tuple ):std::tuple< Args... >( tuple ) {} Identifier() {} Identifier(std::convertible_to<std::tuple<Args...>> auto&& identifier) : std::tuple<Args...>(std::forward<decltype(identifier)>(identifier)) {} // 成员版get函数 template<std::size_t I> constexpr auto& get() & noexcept { return std::get<I>(*static_cast<std::tuple<Args...>*>(this)); } template<std::size_t I> constexpr const auto& get() const & noexcept { return std::get<I>(*static_cast<const std::tuple<Args...>*>(this)); } template<std::size_t I> constexpr auto&& get() && noexcept { return std::get<I>(*static_cast<std::tuple<Args...>*>(this)); } }; // 可选:如果需要支持全局get,也可以添加和方案一一样的全局函数 template<std::size_t I, typename... Args> constexpr auto& get(Identifier<Args...>& id) noexcept { return id.template get<I>(); } // 其余const和右值版本的全局get同理 int main() { Identifier<std::wstring, std::int64_t> id(L"id", 10); auto& [ str_id, num_id ] = id; // 正常工作 std::tuple< std::wstring, int64_t > tuple_id(L"id", 10); auto& [ str_t_id, num_t_id ] = tuple_id; return 0; }
原因说明
虽然Identifier继承了std::tuple,但结构化绑定对tuple-like类型的判定规则要求类型必须显式提供:
- 元素数量(通过
std::tuple_size特化或类内value常量) - 每个索引的元素类型(通过
std::tuple_element特化或类内tuple_element_t别名) - 可通过
get<I>(obj)获取对应索引的元素(全局或成员函数)
基类的这些特性不会自动被派生类继承,所以必须手动补充上述定义,编译器才会将Identifier视为可分解的tuple-like类型。
内容的提问来源于stack exchange,提问作者Vladimir Shttl
相关产品推荐
相关产品推荐

