在定义泛型 fallback 时,我遇到了传统SFINAE(使用type_traits
和std::void_t
)与现代C++20 概念operator<<
之间的意外行为差异。目的很简单:创建一个泛型,仅当通过参数相关查找 (ADL)operator<<
未找到任何现有自定义定义时才启用。operator<<
使用特征( )的传统基于SFINAEis_std_streamable
的检测工作符合预期,其定义为:
template <class T, class = void>
struct is_std_streamable : std::false_type {};
template <class T>
struct is_std_streamable<T, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const T&>())>> : std::true_type {};
基于概念的检测(StdStreamable
)定义为:
template <class T>
concept StdStreamable = requires(const T t, std::ostream& os) {
{ os << t } -> std::same_as<std::ostream&>;
};
通用的后备operator<<
看起来像这样(requires
注释掉的子句):
template <StdPrintable T>
// requires(!StdStreamable<T>)
// requires(!is_std_streamable<T>::value)
std::enable_if_t<!is_std_streamable<T>::value, std::ostream&>
operator<<(std::ostream& os, T const& val) {
...
}
取消注释基于概念requires
的子句(requires(!StdStreamable<T>)
或)时requires(!is_std_streamable<T>::value)
,GCC和Clang都会产生以下循环约束错误:
error: satisfaction of constraint 'StdStreamable<T>' depends on itself
我理解,在定义新版本的子句std::declval<std::ostream&>() << std::declval<const T&>()
中使用表达式可能会被编译器解释为循环依赖。但为什么C++20 概念会触发此循环约束问题,而传统的SFINAE不会?这种行为是标准规定的,还是概念的已知限制,或者可能是编译器错误?requires
operator<<
完整、最小、可重现的示例和其他详细信息:
提前致谢。