I am fighting with SFINAE trying to have many functions that requires just to have access to the type T with operator []
.
So far I have the following code that compiles and works fine with Visual Studio 2017:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <list>
#include <array>
#include <map>
#include <set>
using namespace std;
template <typename T, typename X = std::enable_if_t <std::is_array<T>::value || std::is_pointer<T>::value> >
void DoIt(T& c)
{}
template <typename T, typename std::enable_if_t< std::is_same<std::random_access_iterator_tag,
typename std::iterator_traits<typename T::iterator>::iterator_category>::value, bool> = true >
void DoIt(T& c)
{}
int main()
{
int* a;
const int* ac;
int b[10];
array<int,6> c;
vector<int> d;
string s;
DoIt(a); // Ok, compile pass
DoIt(ac); // Ok, compile pass
DoIt(b); // Ok, compile pass
DoIt(c); // Ok, compile pass
DoIt(d); // Ok, compile pass
DoIt(s); // Ok, compile pass
int i;
float f;
map<int, int> m;
//DoIt(f); // Ok, compile fails
//DoIt(i); // Ok, compile fails
// DoIt(m); // Ok, compile fails
return 0;
}
Now I need the following :
How to combine both SFINAE conditions checking for array & pointer and random access operator into one check?
I have many functions and it is not convenient and too much code to have two declarations. But I somehow failed to combine the conditions in a single std::enable_if_t
or in a template structure.
Is it possible above to be extended and to check also and for the container type, so that for example:
vector<int> a;
vector<string> b;
int* c;
string* d;
DoIt(a); // must pass
DoIt(c); // must pass
DoIt(b); // must fail
DoIt(d); // must fail
How to combine both SFINAE conditions checking for array & pointer and random access operator into one check? I
The simplest way that come in my mind is check if you can write c[0u]
template <typename T>
auto DoIt(T& c) -> decltype( c[0u], void() )
{}
Not a perfect solution: works with types accepting an unsigned integer for as argument for operator[]
(std::vector
s, std::array
s, C-style arrays, pointers, std::map
s with an integer key) but fails with maps with keys incompatibles with unsigned integers.
You can reduce this problem adding a template parameter for the key type (defaulting it to std::size_t
)
template <typename K = std::size_t, typename T>
auto DoIt(T& c) -> decltype( c[std::declval<K>()], void() )
{}
so works as follows
std::array<int,6> c;
DoIt(c); // Ok, compile pass, no needs to explicit the key type
std::map<std::string, int> m;
DoIt(m); // compilation error: std::size_t is a wrong key type
DoIt<std::string>(m); // Ok, compile: std::string is a good key type
If you want enable the function checking also the type returned by the operator []
... well... conceptually is simple but require a little typewriting
I propose the following DoIt2()
function where you have to explicit the required type for operator []
and std::size_t
remain the default type for the argument of the operator (but you can explicit a different type)
template <typename V, typename K = std::size_t, typename T>
std::enable_if_t<
std::is_same_v<V,
std::remove_const_t<
std::remove_reference_t<
decltype(std::declval<T>()[std::declval<K>()])>>>>
DoIt2 (T &)
{}
The idea is simple: get the type of std::declval<T>()[std::declval<K>()]
, remove reference (if present), remove const
(if present) and check if the resulting type is equal to V
(the requested type)
You can use DoIt2()
as follows
std::vector<int> v1;
std::vector<float> v2;
DoIt2<int>(v1); // compile
//DoIt2<int>(v2); // compilation error
//DoIt2<float>(v1); // compilation error
DoIt2<float>(v2); // compile
std::map<int, std::string> m1;
std::map<std::string, float> m2;
DoIt2<std::string, int>(m1);
DoIt2<float, std::string>(m2);