带SFINAE的模板模板实参推导问题:foo与foo2推导失败
解决模板模板实参同时推导foo和foo2的问题
首先,问题的核心在于模板模板参数的形参与实参的参数数量不匹配,以及SFINAE引入的额外模板参数干扰了推导过程。当你移除SFINAE部分后,span变成了单参数模板,刚好匹配foo和foo2期望的单参数模板模板参数,所以推导成功。下面给出几种可行的修复方案:
方案1:调整foo/foo2的模板模板参数,兼容span的双参数形式
你的span主模板是双参数(T + 一个默认的void参数),而foo/foo2的模板模板参数只接受单参数模板,这就导致推导失败。我们只需要把foo/foo2的模板模板参数改成接受带默认参数的双参数模板即可:
#include <type_traits> template<typename T> using enable_if_t_const = typename std::enable_if<std::is_const<T>::value>::type; template<typename T, typename=void> class span; // const T的特化 template<typename T> class span<T, enable_if_t_const<T>> { public: explicit span(const T*) {} }; // 非const T的特化 template<typename T> class span<T, std::enable_if_t<!std::is_const<T>::value>> { public: explicit span(T*) {} }; // 修改模板模板参数为双参数(带默认值) template<template<typename, typename=void> class S> void foo(S<int>) { /* ... */ } template<template<typename, typename=void> class S> void foo2(S<const int>) { /* ... */ } int main() { int arr[5]; const int c_arr[5]; foo(span<int>(arr)); // 推导成功 foo2(span<const int>(c_arr)); // 推导成功 }
这样修改后,S<int>会自动使用span的第二个默认参数void,匹配非const的特化;S<const int>则会匹配到enable_if_t_const<T>的特化版本,推导逻辑完全正常。
方案2:用C++20概念替代SFINAE,简化span的模板结构
如果你可以使用C++20或更高版本,用概念(Concepts)替代SFINAE来约束span的特化,能让模板结构更清晰,同时避免额外模板参数带来的推导问题:
#include <type_traits> #include <concepts> template<typename T> concept ConstType = std::is_const_v<T>; template<typename T> requires ConstType<T> class span { public: explicit span(const T*) {} }; template<typename T> requires (!ConstType<T>) class span { public: explicit span(T*) {} }; // 这里依然使用单参数的模板模板参数,和原来的foo/foo2一致 template<template<typename> class S> void foo(S<int>) { /* ... */ } template<template<typename> class S> void foo2(S<const int>) { /* ... */ } int main() { int arr[5]; const int c_arr[5]; foo(span<int>(arr)); // 推导成功 foo2(span<const int>(c_arr)); // 推导成功 }
概念通过requires子句直接约束模板的适用范围,不需要额外的模板参数,span本质上还是单参数模板,完美匹配foo和foo2的模板模板参数要求,推导自然没问题。
方案3:重构span的特化逻辑,隐藏SFINAE参数
如果必须保留SFINAE且不能修改foo/foo2的结构,你可以把span包装成一个单参数模板,内部用继承的方式复用原来的SFINAE版本:
#include <type_traits> template<typename T, typename=void> class span_impl; template<typename T> class span_impl<T, typename std::enable_if<std::is_const<T>::value>::type> { public: explicit span_impl(const T*) {} }; template<typename T> class span_impl<T, std::enable_if_t<!std::is_const<T>::value>> { public: explicit span_impl(T*) {} }; // 对外暴露的单参数span,内部继承span_impl template<typename T> class span : public span_impl<T> { using span_impl<T>::span_impl; }; // 原来的foo/foo2不需要修改 template<template<typename> class S> void foo(S<int>) { /* ... */ } template<template<typename> class S> void foo2(S<const int>) { /* ... */ } int main() { int arr[5]; const int c_arr[5]; foo(span<int>(arr)); // 推导成功 foo2(span<const int>(c_arr)); // 推导成功 }
这种方式把带SFINAE的双参数span_impl隐藏起来,对外提供单参数的span,既保留了原来的SFINAE逻辑,又能让foo/foo2的模板模板参数正常推导。
内容的提问来源于stack exchange,提问作者R zu
相关产品推荐
相关产品推荐

