I want to write a type trait which uses SFINAE to check a type for the existence of a subscript expression. My initial attempt below seems to work when the subscript expression is possible but does not work when the bracket operator does not exist.
#include <iostream>
#include <vector>
#include <cassert>
template<class T, class Index>
struct has_subscript_operator_impl
{
template<class T1,
class Reference = decltype(
(*std::declval<T*>())[std::declval<Index>()]
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
template<class>
static std::false_type test(...);
using type = decltype(test<T>(0));
};
template<class T, class Index>
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
struct doesnt_have_it {};
struct returns_void
{
void operator[](int) {}
};
struct returns_int
{
int operator[](int) { return 0; }
};
int main()
{
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
assert((!has_subscript_operator<doesnt_have_it,int>::value));
std::cout << "has_subscript_operator<returns_void,int>: " << has_subscript_operator<returns_void,int>::value << std::endl;
assert((!has_subscript_operator<returns_void,int>::value));
std::cout << "has_subscript_operator<returns_int,int>: " << has_subscript_operator<returns_int,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
std::cout << "has_subscript_operator<int*,int>: " << has_subscript_operator<int*,int>::value << std::endl;
assert((has_subscript_operator<int*,int>::value));
std::cout << "has_subscript_operator<std::vector<int>,int>: " << has_subscript_operator<std::vector<int>,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
return 0;
}
clang-3.4
's output:
$ clang -std=c++11 -I. -lstdc++ test_has_subscript_operator.cpp
test_has_subscript_operator.cpp:10:14: error: type 'doesnt_have_it' does not provide a subscript operator
(*std::declval<T*>())[std::declval<Index>()]
^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
test_has_subscript_operator.cpp:25:1: note: in instantiation of template class 'has_subscript_operator_impl<doesnt_have_it, int>' requested here
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
^
test_has_subscript_operator.cpp:41:66: note: in instantiation of template type alias 'has_subscript_operator' requested here
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
^
1 error generated.
How can I fix my has_subscript_operator
such that it works correctly for all types?
With C++20 Concepts:
template<class T, class I = std::size_t>
concept indexible = requires (T& t, const I& i) {
{t[i]};
};
Usage:
static_assert(not indexible<std::list<int>>);
static_assert( indexible<std::string>);
static_assert( indexible<std::vector<bool>>);
static_assert(not indexible<std::map<std::string, int>>);
static_assert( indexible<std::map<std::string, int>, std::string>);
static_assert(not indexible<const std::map<std::string, int>, std::string>);
struct any {
template<class T>
operator const T&() const;
};
static_assert( indexible<std::map<std::string, int>, any>);
static_assert( indexible<std::map<char*, int>, any>);
struct Foo {
int operator[](int i);
int operator[](double i);
};
static_assert(not indexible<Foo, any>);
Live demo: https://godbolt.org/z/7GWK4PvM8
Note that the usage with any
will fail if operator[]
is overloaded with different parameter types.
Once you have C++11, it's a lot easier to write type traits... you don't need to use the ellipsis overload trick. You can just use your decltype
expression directly with the help of the magical:
template <typename... >
using void_t = void;
We have our base case:
template<class T, class Index, typename = void>
struct has_subscript_operator : std::false_type { };
And our expression SFINAE valid case:
template<class T, class Index>
struct has_subscript_operator<T, Index, void_t<
decltype(std::declval<T>()[std::declval<Index>()])
>> : std::true_type { };
And then you can write the same alias:
template <class T, class Index>
using has_subscript_operator_t = typename has_subscript_operator<T, Index>::type;
You can also use @Yakk's favorite method, which given this boilerplate that he copies in every answer:
namespace details {
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
You can then simply write properties:
template <class T, class Index>
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);
template <class T, class Index>
using has_subscript = can_apply<subscript_t, T, Index>;
SFINAE only works when substitution failure happens in the immediate context. The template parameter Index
is already known by the time the member function template test
is being instantiated, so instead of substitution failure you get a hard error.
The trick to working around this is to deduce Index
again by adding an additional template type parameter to test
and default it to Index
.
template<class T1,
class IndexDeduced = Index, // <--- here
class Reference = decltype(
(*std::declval<T*>())[std::declval<IndexDeduced>()] // and use that here
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
Now your code works as intended.
Live demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With