Some general suggestion
I would avoid using namespace std
which your code implies (almost) that you use.
You can shorten and make more readable std::enable_if<T>::type
and std::is_integral<T>::value
by writing them as std::enable_if_t<T>
and std::is_integral_v<T>
respectively (search for "Helper types" here, for instance).
The answer to your question
You need to apply operator[]
to something
What you really want to ask is if the items in the object of type V
can be accessed via []
, so you want to check whether something like V{}[0]
is valid, so you would write something like this (as to why std::decay_t
is needed, see note (¹)):
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(V{}[0])>>, int> = false>
But V
might be not default constructible
If you want to support types V
without assuming that they are default constructible you can use std::declval
to "pretend" you create a value of that type (here std::decay_t
is needed for the same reason as above):
template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(std::declval<V>()[0])>>, int> = false>
And operator[]
could return some proxy to the actual entities in V
In general operator[]
doesn't return an object of the type which is "conceptually" the type stored in the container; for instance, for std::vector<bool>
, operator[]
doesn't return a bool&
, but a proxy type²; in those cases, you really want to rely on the value_type
type alias stored in the container:
template<class V, typename std::enable_if_t<std::is_integral_v<typename V::value_type>, int> = false>
Something might be not value_type
-equipped but only provide .begin()
/.end()
As a last improvement, we can also remove the assumption that the V
has a value_type
member alias (which is true for STL container), and simply require that it has a begin
member function (STL container have it too), and make use of 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>
Notice that
ranges::iter_value_t<decltype(std::declval<V>().begin())>
is in general not the same same thing as
std::decay_t<decltype(*std::declval<V>().begin())>
a case of when they are different being, again, std::vector<bool>
.
¹ std::vector<int>::operator[]
, for instance, returns int&
, not int
, and int&
is not integral:
static_assert(std::is_integral_v<int&>); // fails
² For instance:
// 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
is from the header #include <range/v3/iterator/access.hpp>
of the Range-v3 library. The documentation of that library is close to crap, so you can refer the doc of the analagous C++20 functionality on cppreference.