You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让继承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类型的判定规则要求类型必须显式提供:

  1. 元素数量(通过std::tuple_size特化或类内value常量)
  2. 每个索引的元素类型(通过std::tuple_element特化或类内tuple_element_t别名)
  3. 可通过get<I>(obj)获取对应索引的元素(全局或成员函数)

基类的这些特性不会自动被派生类继承,所以必须手动补充上述定义,编译器才会将Identifier视为可分解的tuple-like类型。

内容的提问来源于stack exchange,提问作者Vladimir Shttl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:57:36