SFINAE在返回类型生效,参数类型中为何无法匹配函数?
问题解析:std::enable_if置于参数类型时推导失败的原因
你遇到的问题核心在于模板参数推导中的非推导上下文,当std::enable_if出现在函数参数的模板参数内部时,关键模板参数T无法被编译器正确推导,最终导致模板匹配失败,抛出"no matching function for call"错误。
具体原因分析
先看你最初的Length函数模板:
template <typename T, size_t N, size_t M> static inline T Length( const Matrix<typename std::enable_if<(M == 1 || N == 1), T>::type, N, M> & input)
当你传入Matrix<double, 3, 1>这类实例时,编译器需要推导模板参数T、N、M:
N和M可以从Matrix的第二、第三个模板参数顺利推导(比如这里N=3,M=1);- 但
T所在的位置是typename std::enable_if<...>::type,这属于C++模板推导中的非推导上下文——编译器无法从传入的Matrix<double, ...>的第一个模板参数double反向推导出T,因为这个嵌套类型的解析依赖于T本身,形成了推导循环,最终导致整个模板推导失败。
而当你把enable_if移到返回类型时:
template <typename T, size_t M, size_t N> static inline typename std::enable_if<(M == 1 || N == 1), T>::type Length(const Matrix<T, N, M> & input)
推导逻辑完全正常:
- 首先,
T、M、N可以直接从传入的Matrix实例的模板参数推导出来(比如T=double,N=3,M=1); - 之后编译器检查
enable_if的条件是否成立,若成立则保留该模板(返回类型为T),若不成立则通过SFINAE机制自动排除该模板,不会触发编译错误。
为什么CrossProduct可以正常工作?
你的CrossProduct函数签名是:
template <typename T, size_t M, size_t N> static inline Matrix<typename std::enable_if<(M == 3 && N == 1) || (M == 1 && N == 3), T>::type , M, N> CrossProduct(const Matrix<T, M, N> & a, const Matrix<T, M, N> & b)
这里enable_if虽然放在返回类型的Matrix模板参数里,但函数参数是const Matrix<T, M, N>&——T、M、N可以直接从函数参数推导出来,之后再验证返回类型的enable_if条件,逻辑和Length放在返回类型的情况一致,因此可以正常工作。
解决方案
方案1:将enable_if移到模板参数列表(兼容C++11及以上)
把enable_if作为默认模板参数,既避免非推导上下文问题,又能实现SFINAE:
template <typename T, size_t N, size_t M, typename = typename std::enable_if<(M == 1 || N == 1)>::type> static inline T Length(const Matrix<T, N, M> & input) { T value = 0; for (size_t i = 0; i < std::max(N, M); ++i) { value += (input[i] * input[i]); } return std::sqrt(value); }
方案2:使用C++17的requires约束(推荐)
如果你的编译器支持C++17,用requires表达式可以写出更清晰的代码:
template <typename T, size_t N, size_t M> requires (M == 1 || N == 1) static inline T Length(const Matrix<T, N, M> & input) { T value = 0; for (size_t i = 0; i < std::max(N, M); ++i) { value += (input[i] * input[i]); } return std::sqrt(value); }
内容的提问来源于stack exchange,提问作者Avi Ginsburg
相关产品推荐
相关产品推荐

