一些一般性建议
我会避免 using namespace std 你的代码暗示(几乎)你使用。
您可以将std::enable_if<T>::type 和std::is_integral<T>::value 分别写为std::enable_if_t<T> 和std::is_integral_v<T> 来缩短它们并使它们更具可读性(例如,搜索“Helper types”here)。
你的问题的答案
您需要将operator[] 应用于某事
你真正想问的是V类型的对象中的项目是否可以通过[]访问,所以你想检查V{}[0]这样的东西是否有效,所以你会写这样的东西(至于为什么需要std::decay_t,见注(¹)):
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(V{}[0])>>, int> = false>
但V 可能不是默认可构造的
如果你想支持V 类型而不假设它们是默认可构造的,你可以使用std::declval 来“假装”你创建了该类型的值(这里需要std::decay_t,原因与上述相同) :
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(std::declval<V>()[0])>>, int> = false>
而operator[] 可以将一些代理返回给V 中的实际实体
一般operator[] 不会返回“概念上”存储在容器中的类型的对象;例如,对于std::vector<bool>,operator[] 不返回bool&,而是一个代理类型²;在这些情况下,您真的想依赖存储在容器中的 value_type 类型别名:
template<class V, typename std::enable_if_t<std::is_integral_v<typename V::value_type>, int> = false>
某些东西可能没有value_type-equipped,但只提供.begin()/.end()
作为最后的改进,我们还可以删除V 具有value_type 成员别名的假设(这对于STL 容器是正确的),并且只要求它具有begin 成员函数(STL 容器也有),并利用ranges::iter_value_t³:
template<class V, typename std::enable_if_t<std::is_integral_v<ranges::iter_value_t<decltype(std::declval<V>().begin())>>, int> = 0>
注意
ranges::iter_value_t<decltype(std::declval<V>().begin())>
通常与
不同
std::decay_t<decltype(*std::declval<V>().begin())>
当它们不同时,又是std::vector<bool>。
¹std::vector<int>::operator[],例如,返回int&,而不是int,并且int&不是积分:
static_assert(std::is_integral_v<int&>); // fails
² 例如:
// this passes
static_assert(std::is_same_v<int, std::decay_t<decltype(std::declval<std::vector<int>>()[0])>>);
// this doesn't!
static_assert(std::is_same_v<bool, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);
// with my compiler, this one does pass
static_assert(std::is_same_v<std::_Bit_reference, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);
³ ranges::iter_value_t 来自 Range-v3 库的标头 #include <range/v3/iterator/access.hpp>。该库的文档几乎是废话,因此您可以参考cppreference 上的类似 C++20 功能的文档。