C++ 为什么 SFINAE 仅使用 class 模板参数失败?
C++ why does SFINAE fail with only a class template parameter?
我以 的风格使用 SFINAE,以便通过使用适当的成员函数调用通用矢量对象。例如,下面的代码首先调用 operator[](int) const
,如果不存在则调用 operator()(int) const
:
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, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const
{
return v[i];
}
template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
auto get(V const& v, int i, rank<1>) const
{
return v(i);
}
VectorType v;
};
根据 this thread, the whole compiles and seems to work 中的建议设置 has_bracket_operator
和 has_parenthesis_operator
特征。
然而,将成员向量传递给重载的 class 模板似乎从一开始就没有必要,所以我尝试设置相同但不传递它。为此,我将模板参数 V
替换为用于设置 class 模板的 VectorType
参数:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
{
return v[i];
}
template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> >
auto get(int i, rank<1>) const
{
return v(i);
}
然而,现在编译失败(在 gcc 5.1.0 中)并出现以下错误消息:
/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]':
main.cpp:46:10: required from 'struct VectorWrapper<std::vector<int> >'
main.cpp:59:38: required from here
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
问题:
- 这个编译错误的原因是什么?
- 除了我的第一个代码块之外,还有合适的解决方法吗? (也就是说,保留通常的编码风格——其中一个不必传递成员)。
在您的失败示例中,模板参数 VectorType
已在解析 get 时确定。要使 SFINAE 工作,您需要在该方法调用时使用于 SFINAE 的模板参数解析。以下是对您的第一个示例的修改,以按您希望的方式工作:
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;
};
这样,V
在调用get
时就解决了,会正确使用SFINAE。
SFINAE 来自 [temp.deduct]/8,强调我的:
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.
直接上下文是模板声明中的内容。在您的初始示例中:
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
在直接上下文中,因此 enable_if
上的替换失败只是推导失败。
然而,在你的第二个例子中:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
VectorType
不在 get
的直接上下文中,所以这里的失败不会是演绎失败,而是硬错误。
除非 VectorType
恰好拥有所有这些运算符。
解决任何模板问题的方法就是添加更多模板。在这种情况下,通过引入另一种类型强制 VectorType
在直接上下文中:
template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const
并调用 get<>()
。
或者您可以只使用 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);
}
我以 operator[](int) const
,如果不存在则调用 operator()(int) const
:
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, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const
{
return v[i];
}
template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
auto get(V const& v, int i, rank<1>) const
{
return v(i);
}
VectorType v;
};
根据 this thread, the whole compiles and seems to work 中的建议设置 has_bracket_operator
和 has_parenthesis_operator
特征。
然而,将成员向量传递给重载的 class 模板似乎从一开始就没有必要,所以我尝试设置相同但不传递它。为此,我将模板参数 V
替换为用于设置 class 模板的 VectorType
参数:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
{
return v[i];
}
template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> >
auto get(int i, rank<1>) const
{
return v(i);
}
然而,现在编译失败(在 gcc 5.1.0 中)并出现以下错误消息:
/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]':
main.cpp:46:10: required from 'struct VectorWrapper<std::vector<int> >'
main.cpp:59:38: required from here
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
问题:
- 这个编译错误的原因是什么?
- 除了我的第一个代码块之外,还有合适的解决方法吗? (也就是说,保留通常的编码风格——其中一个不必传递成员)。
在您的失败示例中,模板参数 VectorType
已在解析 get 时确定。要使 SFINAE 工作,您需要在该方法调用时使用于 SFINAE 的模板参数解析。以下是对您的第一个示例的修改,以按您希望的方式工作:
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;
};
这样,V
在调用get
时就解决了,会正确使用SFINAE。
SFINAE 来自 [temp.deduct]/8,强调我的:
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.
直接上下文是模板声明中的内容。在您的初始示例中:
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
在直接上下文中,因此 enable_if
上的替换失败只是推导失败。
然而,在你的第二个例子中:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
VectorType
不在 get
的直接上下文中,所以这里的失败不会是演绎失败,而是硬错误。
除非 VectorType
恰好拥有所有这些运算符。
解决任何模板问题的方法就是添加更多模板。在这种情况下,通过引入另一种类型强制 VectorType
在直接上下文中:
template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const
并调用 get<>()
。
或者您可以只使用 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);
}