SFINAE - 检测类型 T 是指针、数组还是带有随机访问运算符的容器,以及给定的值类型



我正在与 SFINAE 作斗争,试图拥有许多只需要使用运算符[]访问 T 类型的功能。 到目前为止,我有以下代码可以编译并与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;
}

现在我需要以下内容:

  1. 如何将数组和指针的SFINAE条件检查以及随机访问运算符合并为一个检查? 我有很多函数,有两个声明不方便且代码太多。但是我不知何故未能在单个std::enable_if_t或模板结构中组合条件。

  2. 上面是否可以扩展并检查容器类型,例如:

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

如何将数组和指针的SFINAE条件检查以及随机访问运算符合并为一个检查?我

我想到的最简单的方法是检查你是否可以写c[0u]

template <typename T>
auto DoIt(T& c) -> decltype( c[0u], void() )
{}

不是一个完美的解决方案:适用于接受无符号整数作为operator[]参数的类型(std::vectors、std::arrays、C 样式数组、指针、带有整数键的std::maps(,但对于键与无符号整数不兼容的映射失败。

您可以减少此问题,为密钥类型添加模板参数(默认为std::size_t(

template <typename K = std::size_t, typename T>
auto DoIt(T& c) -> decltype( c[std::declval<K>()], void() )
{}

所以工作如下

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

如果要启用函数,还要检查运算符返回的类型[]...井。。。概念上很简单,但需要一点打字

我建议以下DoIt2()函数,您必须显式运算符[]所需的类型std::size_t并且保持运算符参数的默认类型(但您可以显式指定不同的类型(

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 &)
{}

这个想法很简单:获取std::declval<T>()[std::declval<K>()]的类型,删除引用(如果存在(,删除const(如果存在(并检查结果类型是否等于V(请求的类型(

您可以按如下方式使用DoIt2()

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);

当你有一堆想要应用的条件 SFINAE 时,我通常会尝试将它们拆分为较小的辅助结构。

在 yoyur 示例中,它看起来像这样。

template <typename T, typename U = void>
struct random_access : std::false_type {};
template <typename T>
struct random_access<T, std::enable_if_t<std::is_same_v<std::random_access_iterator_tag,
typename std::iterator_traits<typename T::iterator>::iterator_category>>> : std::true_type {};
template <typename T>
struct random_access<T, std::enable_if_t<std::is_pointer_v<T> || std::is_array_v<T>>> : std::true_type {};
template <typename T>
constexpr bool random_access_v = random_access<T>::value;
template <typename T, typename U = void>
struct contains_container : std::false_type {};
template <typename T>
struct contains_container<T, std::enable_if_t<std::is_same_v<std::void_t<decltype(std::declval<T&>()[0][0])>, void>>> : std::true_type {};
template <typename T>
constexpr bool contains_container_v = contains_container<T>::value;
template <typename T, std::enable_if_t<random_access_v<T> && !contains_container_v<T>, bool> = true>
void DoIt(T&) {}

contains_container是我解决第二部分的诱惑。不确定这是否正是您要找的,但它会检查是否可以将两层operator[]应用于T。这将使您的第二个示例通过。

最新更新