I'm trying to use template to check if input type implements operator[]
. Here is my code:
#include <iostream>
#include <vector>
using namespace std;
template <typename T, typename U = void>
struct has_bracket
{
static constexpr int value = 0;
};
template <typename T>
struct has_bracket<T, decltype(T::operator[])>
{
static constexpr int value = 1;
};
But it didn't work. It always output 0 no matter which type I input.
struct Test
{
int operator[](size_t n) { return 0; }
};
struct CTest
{
int operator[](size_t n) const { return 0; }
};
int main()
{
cout << has_bracket<int>::value << endl; // output: 0
cout << has_bracket<double>::value << endl; // output: 0
cout << has_bracket<Test>::value << endl; // output: 0
cout << has_bracket<CTest>::value << endl; // output: 0
cout << has_bracket<vector<int>>::value << endl; // output: 0
return 0;
}
I think that if T = int
or T = double
, decltype(&T::operator[])
will fail and the primary has_bracket
will be used according to SFINAE. If T = Test
or T = CTest
or T = vector<int>
, the specialization one will be instantiated, leads to the has_bracket<T>::value
be 1.
Is there something wrong? How to fix this problem to let has_bracket<T>
be 1 for T
= Test
, CTest
and vector<int>
?
CodePudding user response:
Thats not how SFINAE works. has_bracket<int>
does not explicitly specify second template argument, default is void
, hence it is has_bracket<int,void>
.
decltype(T::operator[])
is never void
. Hence, you always get the primary template. Moreover decltype(T::operator[])
would require operator[]
to be a static member.
SFINAE works when the specialisation has void
as second argument for the "true" case, because thats the default of the primary template. std::void_t
can be handy to have a type that is either void
or a substitution failure:
template <typename T, typename U = void>
struct has_bracket
{
static constexpr int value = 0;
};
template <typename T>
struct has_bracket<T, std::void_t<decltype(std::declval<T>()[std::size_t{1}])> >
{
static constexpr int value = 1;
};
If you are resitricted to < C 17, ie you cannot use std::void_t
you can replace it with a handwritten (taken from cppreference):
template< typename... Ts > struct make_void { typedef void type; }; template< typename... Ts > using void_t = typename make_void<Ts...>::type;
Summarizing from comments:
- The main issue with your code is that the second argument of the specialization is not
void
, hence it is never choosen. - Your code uses
decltype(T::operator[])
and irrespective of the first bullet, this requires the operator to be static (see here) - The issue with
decltype(&T::operator[])
is that you cannot take the address when there is more than one overload (see here) - The previous solution in this answer uses
decltype(std::declval<T>()[0])
which requires thatoperator[]
can be called with0
as argument. That this "works" withstd::map<std::string,int>::operator[]
is an unfortunate coincidence, because a null pointer can be converted tostd::string
. It does not work whenoperator[]
takes an argument that cannot be constructed from0
(see here). - For this reason I changed the code above to
decltype(std::declval<T>()[std::size_t{1}])
. The literal1
does not have the problem of implicit conversion to a null pointer. The solution now only detectsoperator[]
that can be called with an integer.