为std::apply特化std::tuple_size与std::get时遭遇编译错误
为结构体特化std::tuple_size/std::get后,std::apply调用失败的问题
我尝试为一个简单结构体特化std::tuple_size和std::get,想配合std::apply在泛型函数中使用,但调用std::apply时编译报错。有意思的是,把std::apply和std::__apply_impl的实现复制到本地命名空间后调用却能正常运行。请问我哪里操作错了?
最小可复现代码
#include <iostream> #include <array> #include <tuple> #include <functional> // 定义一个包含不同类型成员的简单结构体A struct A { int i; double d; }; // 为A特化std::tuple_size和std::get namespace std { template <> struct tuple_size<A> : std::integral_constant<std::size_t, 2> { }; // 注释里的"3 members in A"是笔误,实际是2个成员 template <std::size_t N> constexpr decltype(auto) get (const A& a) { if constexpr (N == 0) return (a.i); else if constexpr (N == 1) return (a.d); } } // 复制std::__apply_impl和std::apply的实现到本地命名空间My namespace My { template <class Callable, class Tuple, size_t... _Indices> constexpr decltype(auto) apply_impl(Callable&& _Obj, Tuple&& _Tpl,std::index_sequence<_Indices...>) { using namespace std; return std::invoke (forward<Callable>(_Obj), get<_Indices>(forward<Tuple>(_Tpl))...); } template <class Callable, class Tuple> constexpr decltype(auto) apply(Callable&& Obj, Tuple&& Tpl) { using namespace std; return apply_impl(forward<Callable>(Obj), forward<Tuple>(Tpl), make_index_sequence<tuple_size_v<remove_reference_t<Tuple>>>{}); } } int main () { A a {0, 1.5}; // #define COMPILER_ERROR // 打开这个宏会触发编译错误 #ifdef COMPILER_ERROR std::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a); #else My::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a); #endif return 0; }
问题原因
你的代码存在两个核心问题:
- 只特化了const版本的std::get:你仅提供了
const A&参数的std::get特化,但std::apply处理非const左值(比如main里的a)时,标准库实现会尝试匹配非const版本的get,而你没有提供这个重载,导致编译失败。 - 本地My::apply能运行的原因:你自己实现的
My::apply中,get<_Indices>(forward<Tuple>(Tpl))的参数是A&,而const版本的get可以绑定到非const左值,因此能成功匹配;但标准库的std::apply实现可能对重载匹配的逻辑更严格,或者会优先尝试非const版本的get,导致找不到合适的重载。
解决方法
补充完整std::get的特化版本,覆盖所有cv限定和值类别:
namespace std { template <> struct tuple_size<A> : std::integral_constant<std::size_t, 2> { }; // 非const左值引用版本 template <std::size_t N> constexpr decltype(auto) get(A& a) { if constexpr (N == 0) return (a.i); else if constexpr (N == 1) return (a.d); } // const左值引用版本(你原来的代码) template <std::size_t N> constexpr decltype(auto) get(const A& a) { if constexpr (N == 0) return (a.i); else if constexpr (N == 1) return (a.d); } // 右值引用版本 template <std::size_t N> constexpr decltype(auto) get(A&& a) { if constexpr (N == 0) return (std::move(a.i)); else if constexpr (N == 1) return (std::move(a.d)); } // const右值引用版本(可选,覆盖所有情况) template <std::size_t N> constexpr decltype(auto) get(const A&& a) { if constexpr (N == 0) return (std::move(a.i)); else if constexpr (N == 1) return (std::move(a.d)); } }
补充完这些特化后,std::apply就能正常调用了。另外,建议修正代码里的注释笔误(把"3 members in A"改成"2 members in A"),避免混淆。
内容的提问来源于stack exchange,提问作者Catriel
相关产品推荐
相关产品推荐

