实现C++ Tuple的get_or函数:索引无效返回默认类型遇编译错误
问题:实现支持无效索引返回默认值的Tuple Get函数
需求与问题重现
想要实现一个类似std::get的tuple操作函数get_or,当索引无效时返回std::monostate实例,预期逻辑如下:
template<size_t I, typename T> auto get_or(T const& t) { // 伪代码逻辑 return (std::get<I>(t) 合法) ? std::get<I>(t) : std::monostate{}; }
尝试实现时,定义了判断tuple类型和索引有效性的trait与concept,但调用无效索引时直接触发tuple index out of bounds的static_assert错误,无法返回预期的std::monostate。
尝试的代码实现
- 判断tuple类型的trait与concept:
template<typename T> struct is_tuple : std::false_type {}; template<typename... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type {}; namespace test_concepts { template<typename T> concept is_tuple = requires { ::is_tuple<T>::value; }; }
- 判断索引有效性的函数与concept:
template<size_t I, typename T> constexpr bool can_get (T const&) { return false; } template<size_t I, typename T> requires test_concepts::is_tuple<T> constexpr bool can_get(T const& t) { return (I < std::tuple_size_v<std::remove_cvref_t<decltype(t)>>); } namespace test_concepts { template<size_t I, typename T> concept can_get = requires(T t) { ::can_get<I>(t); }; }
问题原因
- 三元运算符无编译期惰性:普通的
?:运算符会强制编译器实例化两边的表达式,哪怕条件编译期为false。当索引I越界时,std::get<I>(t)会触发标准库内置的static_assert,直接导致编译失败,根本不会走到返回std::monostate的分支。 - Concept逻辑错误:你定义的
can_get概念仅检查::can_get<I>(t)函数是否可调用,而非检查它的返回值是否为true,因此无法过滤掉索引越界的非法情况。
解决方案
方法1:C++20 constexpr if(最简洁)
利用constexpr if的编译期分支特性,只有符合条件的分支会被实例化:
#include <tuple> #include <variant> // 用于std::monostate template<size_t I, typename T> auto get_or(const T& t) { if constexpr (test_concepts::is_tuple<T> && I < std::tuple_size_v<std::remove_cvref_t<T>>) { return std::get<I>(t); } else { return std::monostate{}; } }
直接在编译期判断T是否为tuple且索引合法,只有条件成立时才会实例化std::get<I>(t),彻底避免越界的static_assert。
方法2:SFINAE函数重载(兼容C++17及更早)
通过std::enable_if区分合法与非法索引的重载版本:
#include <tuple> #include <variant> #include <type_traits> // 非法索引或非tuple类型的情况 template<size_t I, typename T, typename = std::enable_if_t<!(test_concepts::is_tuple<T> && I < std::tuple_size_v<std::remove_cvref_t<T>>)>> std::monostate get_or(const T&) { return {}; } // 合法索引的tuple情况 template<size_t I, typename T, typename = std::enable_if_t<test_concepts::is_tuple<T> && I < std::tuple_size_v<std::remove_cvref_t<T>>>> auto get_or(const T& t) { return std::get<I>(t); }
修正Concept(可选)
如果要继续使用concept约束,需修改为直接检查索引合法性:
namespace test_concepts { template<size_t I, typename T> concept can_get = test_concepts::is_tuple<T> && (I < std::tuple_size_v<std::remove_cvref_t<T>>); }
之后结合constexpr if使用,效果与方法1一致。
内容的提问来源于stack exchange,提问作者h.s.
相关产品推荐
相关产品推荐

