SFINAE 多层类型测定

SFINAE with multi-layer type determination

以下代码特化了 f() 的两个版本。第一个检测一个向量和 returns 一个迭代器。第二个采用所有其他类型和 return 一个副本。

这无法在 VC 2010 上编译,GetIter2 中出现 GetIter::type 不存在的错误。这仅在使用非向量解析对 f() 的调用时才会发生。如果我删除一层类型间接,对 return 类型使用 GetIter 而不是 GetIter2(参见注释行),那么一切正常。

我想我想知道这是编译器错误还是正确行为。如果这看起来很奇怪,那是因为它减少了我在使用 boost::range_iterator 时遇到的问题,我不能只删除 GetIter2 代表的内容。

#include <vector>
using namespace std;

template<typename T>
struct GetIter {
};

template<typename T>
struct GetIter<vector<T>> {
    typedef typename vector<T>::iterator type;
};

template<typename T>
struct GetIter2
{
    typedef typename GetIter<T>::type type;
};

template<typename T>
typename enable_if<is_same<T, vector<int>>::value, typename GetIter2<T>::type>::type
//typename enable_if<is_same<T, vector<int>>::value, typename GetIter<T>::type>::type
f(T & t) {
    return t.begin();
}

template<typename T>
typename enable_if<!is_same<T, vector<int>>::value, T>::type
f(T & t) {
    return t;
}

int main(int argc, char* argv[])
{
    vector<int> v(2);
    int i = 6;

    f(v);
    f(i);  // error C2039: 'type' : is not a member of 'GetIter<T>'

    return 0;
}

编辑: 这是我要解决的实际问题。使用迭代器作为第二个参数对 copy() 的第二次调用在 boost::mpl::eval_if_c 对象上导致与上述类似的错误。

#include <vector>
using namespace std;

#include <boost/range.hpp>
#include <boost/tti/has_type.hpp>

BOOST_TTI_TRAIT_HAS_TYPE(has_iterator, iterator)

template<typename InCont, typename Out>
typename enable_if<has_iterator<Out>::value, typename boost::range_iterator<Out>::type>::type
copy(InCont const & in_cont, Out & out_cont)
{
    return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}

template<typename InCont, typename Out>
typename enable_if<!has_iterator<Out>::value, Out>::type
copy(InCont const & in_cont, Out & out_iter)
{
    return std::copy(boost::begin(in_cont), boost::end(in_cont), out_iter);
}

int main(int argc, char* argv[])
{
    vector<int> v1;
    vector<int> v2;

    copy(v1, v2);
    copy(v1, v2.begin());  // error C2039: 'type' : is not a member of 'boost::mpl::eval_if_c<C,F1,F2>'

    return 0;
}

编辑 2: 最初的问题已在 boost::range_iterator 的最新版本中得到修复。一旦我打了补丁,事情就变得容易了。这是我登陆的内容,使用 boost::has_range_iterator:

检查容器
#include <vector>
using namespace std;

#include <boost/range.hpp>

template<typename InCont, typename Out>
typename boost::range_iterator<Out>::type
copy(InCont const & in_cont, Out & out_cont)
{
    return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}

template<typename InCont, typename Out>
typename enable_if<!boost::has_range_iterator<Out>::value, Out>::type
copy(InCont const & in_cont, Out out_iter)
{
    return std::copy(boost::begin(in_cont), boost::end(in_cont), out_iter);
}

int main(int argc, char* argv[])
{
    vector<int> v1;
    vector<int> v2;

    copy(v1, v2);
    copy(v1, v2.begin());

    return 0;
}

我认为非向量 GetIter 遗漏了一个类型函数。 模板 结构 GetIter { typedef T 类型; };

我仍在努力寻找标准的相关部分来解释为什么这不起作用。我认为这与 GetIter2<T> 的替换没有失败但随后访问 GetIter2<T>::type 然后确定 GetIter<T>::type 不存在有关。通过使 GetIter2<T> 模板无法立即替换,您不会收到错误。您可以通过稍微更改其定义来做到这一点:

template<typename T, typename ST = typename GetIter<T>::type>
struct GetIter2
{
    typedef ST type;
};

我能够使用以下代码在不修改提升类型的情况下让您的真实示例工作:

template<typename InCont, typename Out, typename = typename enable_if<has_iterator<Out>::value>::type>
typename boost::range_iterator<Out>::type
copy(InCont const & in_cont, Out & out_cont)
{
    return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}

为什么不起作用

规则是,来自 [temp.deduct],强调我的:

Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

直接上下文就是您在声明本身中看到的内容。当您尝试替换 T=int 时发生的失败不在 typename GetIter2<T>::type 的直接上下文中 - 当尝试确定该类型实际是什么并且看到 GetIter<int> 不存在时发生失败'没有 type 成员。由于这不在直接上下文中,因此这不是演绎失败 - 这是一个硬错误。

注意:即使这是推导失败,它仍然只适用于 std::vector<int, std::allocator<int>>,而不适用于任何矢量。

如何让它工作

实际上,您在这里根本不需要SFINAE。只为 vector 设置一个重载,为所有内容设置另一个重载:

template <class T, class A>
auto f(std::vector<T,A>& t) {
    return t.begin();
}

template <class T>
T f(T& t) {
    return t;
}