Reputation: 385
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>
?
Upvotes: 0
Views: 245
Reputation: 122830
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:
void
, hence it is never choosen.decltype(T::operator[])
and irrespective of the first bullet, this requires the operator to be static (see here)decltype(&T::operator[])
is that you cannot take the address when there is more than one overload (see here)decltype(std::declval<T>()[0])
which requires that operator[]
can be called with 0
as argument. That this "works" with std::map<std::string,int>::operator[]
is an unfortunate coincidence, because a null pointer can be converted to std::string
. It does not work when operator[]
takes an argument that cannot be constructed from 0
(see here).decltype(std::declval<T>()[std::size_t{1}])
. The literal 1
does not have the problem of implicit conversion to a null pointer. The solution now only detects operator[]
that can be called with an integer.Upvotes: 4