SFINAE场景下decltype结合逗号运算符的原理与用法相关疑问
我希望通过本次问询获得全面解答,厘清SFINAE上下文内逗号运算符与decltype配合的工作机制。
已有调研梳理
我搜索了is:question [c++] [decltype] comma相关内容,共找到6个相关问题:
- (2011)How is decltype supposed to work with
operator,:标题匹配度高,但仅针对Visual Studio编译器,唯一答案判定该问题为编译器Bug。 - (2012)How is type deduced from auto return type?:其采纳答案针对表达式
decltype( ( bool( fun(v[0] ) ), void() ) )给出两点说明:
bool( fun(v[0] ) )处于decltype的非求值上下文(与sizeof类似)不会实际执行,但若该子表达式非法则整体表达式非法。void()并非实际值,但在逗号运算符与decltype上下文中可等价视为值使用。
我对上述说明尚未完全理解。
- (2014)c++11 decltype returns reference type:内容深度不足,其采纳答案提到
i, j为左值表达式,decltype会返回其引用类型,意味着, 1可用于规避decltype(x)针对无括号变量名的特殊推导规则,作用类似给x加括号。我想要确认decltype((unparenthesized-name))与decltype(unparenthesized-name, 0)对unparenthesized-name的约束是否一致。 - (2015)Why cannot we use brace initializer in an un-evaluated context?:关联度较低,核心讨论是否可以创建
void类型值的问题。 - (2015)SFINAE:
static_cast<void>()or, void()?:无采纳答案,提问者为高声望用户,我认为现有回答不足以解答疑问。 - (2020)How to use sfinae to exclude types for which a function is defined?:与本次问询主题无关。
目前我仍未完全掌握该技术的完整逻辑。
我当前的理解与未解疑问
根据cppreference中内置逗号运算符的说明:
逗号表达式
E1, E2中,E1会被求值,结果被丢弃,其副作用完成后才会开始求值E2;逗号表达式的类型、值、值类别完全与第二操作数E2一致。
我初步认为代码中E1, E2与单独使用E2的唯一差异是E1的副作用,但该文档未提及逗号运算符在decltype中的使用规则。另外根据decltype文档说明:
decltype推导的目标类型不需要完整、不需要可用析构函数,也可以是抽象类型,但该规则不适用于子表达式:如
decltype(f(g()))中,g()必须是完整类型,f()则不需要。
(我个人认为该规则适用于decltype的全部第二类推导场景,而非仅2.c子项,且此处的f()指代f(g())整体表达式,若理解有误请指正。)
在我此前的提问评论中,用户@cigien提到:向decltype传入f(g())或f(), 0,对f返回类型的完整性要求是一致的。
基于此我推断:decltype(f(), 0)是SFINAE场景下配合std::void_t<>使用的技巧,可同时实现两个效果:
- 校验
f()的合法性,该作用与decltype(f())一致; - 强制要求
f()返回类型为完整类型,该约束是decltype(f())不具备的。
我理解, 0只是将f()作为逗号运算符第一操作数的技巧,替换为decltype(f(), f())也可实现对f的相同约束,只是推导结果为f的返回类型而非int,但传入std::void_t<>时该差异无影响。
但用户HolyBlackCat在回答中提到该逗号技巧并不鲁棒:
若
begin()本身合法且返回完整类型,但逗号运算符,被重载导致无法调用,就会出现假阴性结果。
他提出更优的替代方案是decltype(void(std::declval<T>().begin()))。我尚未理解该方案的工作原理:为什么转换为void可以解决问题?相比直接使用decltype(std::declval<T>().begin())有什么额外作用?
内容的提问来源于stack exchange,提问作者Enlico

