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

SFINAE - detect if type T is pointer, array or container with random access operator and for given value type

我正在与 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

How to combine both SFINAE conditions checking for array & pointer and random access operator into one check? I

我想到的最简单的方法是检查你是否可以写 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 时,我通常会尝试将它们拆分成更小的辅助结构。

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

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。这将使您的第二个示例通过。