基于SFINAE无条件禁用函数:代码合法性及C++版本差异问询
一、原代码的合法性(C++14及后续版本)
先看你给出的这段代码:
#include <type_traits> struct S { template< typename ... Args > void foo() const { static_assert( sizeof...(Args) == 0, "" ); } template< typename ... Args, bool B = false, typename std::enable_if<B, bool>::type = false > void foo() { static_assert( sizeof...(Args) == 0, "" ); } }; int main() { S s{}; s.foo(); }
这段代码在C++14及以后是完全合法的,原因和你提到的标准条款直接相关:
模板的有效性可在任何实例化之前检查。
若无法为某模板或模板内constexpr if语句的子语句生成有效特化,且该模板未被实例化,则程序格式错误,无需诊断。
关键在于:非const版本的foo模板并非完全无法生成有效特化——当你显式指定模板参数B=true时(比如调用s.foo<true>()),std::enable_if<true, bool>::type会解析为bool,此时这个模板特化是完全合法的。因此它不符合标准中“无法生成任何有效特化”的判定条件,编译器不会将其视为格式错误。
另外,在main函数的调用s.foo()中,非const版本的foo会因为B=false触发SFINAE(替换失败并非错误),被从重载决议中排除,最终会选中const版本的foo,这也是符合预期的。
二、针对你的目标:宏控制移除非const版本的foo
你想在编译期定义宏时,把非const的foo从重载决议中移除,这里有几种简洁的实现方式:
方案1:直接用条件编译(最直观,全C++版本兼容)
直接通过#ifdef控制非const版本模板的存在性,定义宏时就不编译这个模板:
#include <type_traits> struct S { template< typename ... Args > void foo() const { static_assert( sizeof...(Args) == 0, "" ); } #ifdef REMOVE_NON_CONST_FOO // 定义宏时,跳过非const版本的定义 #else template< typename ... Args, bool B = false, typename std::enable_if<B, bool>::type = false > void foo() { static_assert( sizeof...(Args) == 0, "" ); } #endif }; int main() { S s{}; s.foo(); }
编译时添加-DREMOVE_NON_CONST_FOO(GCC/Clang)或/DREMOVE_NON_CONST_FOO(MSVC)即可生效。
方案2:用SFINAE绑定宏(无需注释模板,C++14+兼容)
通过宏控制enable_if的条件,让非const版本在宏定义时永远被SFINAE排除:
#include <type_traits> // 用宏控制enable_if的触发条件 #ifdef REMOVE_NON_CONST_FOO #define NON_CONST_FOO_ALLOWED false #else #define NON_CONST_FOO_ALLOWED true #endif struct S { template< typename ... Args > void foo() const { static_assert( sizeof...(Args) == 0, "" ); } template< typename ... Args, bool B = NON_CONST_FOO_ALLOWED, typename std::enable_if<B, bool>::type = false > void foo() { static_assert( sizeof...(Args) == 0, "" ); } }; int main() { S s{}; s.foo(); }
这种方式下,非const模板仍然存在,但永远不会被重载决议选中(除非显式指定B=true,如果不需要这种可能性,方案1更彻底)。
方案3:C++20+用requires子句(更现代的写法)
如果你的项目可以用C++20,用requires子句可以更清晰地控制重载的可用性:
#include <type_traits> struct S { template< typename ... Args > void foo() const { static_assert( sizeof...(Args) == 0, "" ); } #ifdef REMOVE_NON_CONST_FOO // requires false 让这个模板永远无法被选中 template< typename ... Args > void foo() requires false { static_assert( sizeof...(Args) == 0, "" ); } #else template< typename ... Args > void foo() { static_assert( sizeof...(Args) == 0, "" ); } #endif };
内容的提问来源于stack exchange,提问作者tommsch

