使用模板递归检查函数方法是否存在

Recursively check if function method exists using template

本文关键字:函数 方法 是否 存在 检查 递归      更新时间:2023-10-16

出于某些原因,我希望能够做到这一点;

vector<int> p = {1, 2};
vector<vector<int>> q = {p, {0, 1}};
auto t = test(p);
auto u = test(q); // Fails with below implementation

值得注意的是,测试被模板化以接受自定义类,这些类对于一个或两个(目前)维度可能是可迭代的,也可能不是可迭代的。我试图通过检查给定的内容是否具有size函数来确定该做什么;

template<typename T> struct hasSize {
    template<typename U, size_t(U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::size>*);
    template<typename U> static int  Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename iterable> int test(const iterable &x, std::false_type) {
    return (int) x;
}
template<typename iterable> int test(const iterable &x, std:: true_type) {
    int total = 0;
    for(auto &each : x)
        total += test(each,
            std::integral_constant<bool, hasSize<decltype(each)>::value>());
    return total;
}
template<typename iterable> int test(const iterable &view) {
    return test(view, std::true_type());
}

在放弃这个答案后,我根据这里给出的答案建立了hasSize,因为这似乎只适用于成员变量,而不适用于函数。我也尝试了第一次讨论中给出的has_const_reference_op的修改版本,但这也有同样的问题。

给出的错误表明SNIFAE没有第二次应用;

error C2440: 'type cast':
    cannot convert from 'const std::vector<int, std::allocator<_Ty>>' to 'int'
note: No user-defined-conversion operator available that can perform this conversion,
    or the operator cannot be called
note: see reference to function template instantiation
    'int test<iterable>(const iterable &, std::false_type)' being compiled
with iterable = std::vector<int,std::allocator<int>>

但我不知道为什么。

失败的原因是auto&类型的变量实际上是const std::vector<int>&类型,iterable类型是const vector<vector<int>>&类型,因此当使用decltype查询时,它会生成一个引用类型,该引用类型无法通过SFINAE检查size成员函数是否存在。因此,不使用decltype,只需从iterable:中读取value_type即可

total += test(each, std::integral_constant<bool,
                       hasSize<typename iterable::value_type>::value 
//                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
                    >());

或者从decltype:产生的类型中移除引用/常量

total += test(each, std::integral_constant<bool, 
                       hasSize<typename std::decay<decltype(each)>::type>::value
//                                      ~~~~~~~~~^
                    >());

首先,这对于一般的test():来说不是正确的做法

template<typename iterable> int test(const iterable &view) {
    return test(view, std::true_type());
}

因为一般来说test是不可迭代的——这就是我们需要测试的!因此,我们将把它转发到命名空间中的一系列三个函数中,这样我们就可以利用ADL来找到我们需要找到的一切:

namespace adl {
    struct helper {};
    template<typename iterable>
    int test(helper, const iterable &x, std::false_type) {
        return (int) x;
    }
    template<typename iterable>
    int test(helper, const iterable &x, std:: true_type) {
        int total = 0;
        for(auto &each : x) {
            // this calls the general one
            total += test(helper{}, each);
        }
        return total;
    }
    template <typename iterable>
    int test(helper, const iterable& x) {
        return test(helper{}, x, std::integral_constant<bool, hasSize<iterable>::value>{});
    }
}
template<typename iterable>
int test(const iterable &view) {
    return test(adl::helper{}, view);
}

我们需要ADL,这样每个函数都可以找到彼此。


请注意,最好编写产生类型的类型特征,而不仅仅是值。我们是否写了这样的东西:

template <class T, class = void>
struct hasSize : std::false_type { };
template <class T>
struct hasSize<T, void_t<decltype(std::declval<T const&>().size())>>
: std::true_type { };

那么我们的测试仪过载可能会短得多:

template <typename iterable>
int test(helper, const iterable& x) {
    return test(helper{}, x, hasSize<iterable>{});
}

这可能在VS2013上不起作用,但您仍然可以在hasSize中添加type typedef。