C++ why does SFINAE fail with only a class template parameter?

荒凉一梦 提交于 2019-12-01 17:27:04

SFINAE comes to us from [temp.deduct]/8, emphasis mine:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

The immediate context is what's in the template declaration. In your initial example:

template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const

V is in the immediate context, so a substitution failure on the enable_if is just a deduction failure.

However, in your second example:

template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const

VectorType is not in the immediate context of get, so a failure here would not be a deduction failure, it would be a hard error.

Unless VectorType happens to have all of these operators.

The solution to any template problem is to just add more template. In this case, force VectorType to be in the immediate context by introducing another type:

template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const

And call get<>().

In your failing example, the template parameter VectorType has already been determined by the time get is being resolved. To make SFINAE work, you need to make the template parameters you are using for SFINAE resolve at that method call. The following is a modification of your first example to work like you want to:

template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};

template<typename VectorType>
struct VectorWrapper
{
    auto get(int i) const
    {
        return get(v, i, rank<5>());
    }

    template<typename V=VectorType, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
    auto get(int i, rank<2>) const
    {
        return v[i];
    }

    template<typename V=VectorType, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
    auto get(int i, rank<1>) const
    {
        return v(i);
    }

    VectorType v;
};

This way, V is resolved when get is called, and it will correctly use SFINAE.

Or you can just use tag-dispatching:

auto get(int i) const
{
    return get(i, has_bracket_operator<VectorType>(), has_parenthesis_operator<VectorType>());
}

auto get(int i, std::true_type /*brackets*/, std::false_type /*parenthesis*/) const
{
    return v[i];
}

auto get(int i, std::false_type /*brackets*/, std::true_type /*parenthesis*/) const
{
    return v(i);
}

demo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!