Estou encontrando uma diferença inesperada no comportamento entre o SFINAE tradicional (usando type_traits
and std::void_t
) e os conceitos modernos do C++20 ao definir um fallback genérico operator<<
. O propósito é direto: criar um genérico operator<<
que seja habilitado somente se nenhuma definição personalizada existente operator<<
for encontrada via Argument-Dependent Lookup (ADL) .
A detecção baseada em SFINAE da velha escola usando características ( is_std_streamable
) funciona como esperado, definido como:
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 {};
E a detecção baseada em conceitosStdStreamable
( ) é definida como:
template <class T>
concept StdStreamable = requires(const T t, std::ostream& os) {
{ os << t } -> std::same_as<std::ostream&>;
};
O fallback genérico operator<<
se parece com isto ( requires
cláusulas comentadas):
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) {
...
}
Ao descomentar a cláusula baseada em conceitos ( ou ), tanto o GCC quanto o Clang produzem o seguinte erro de restrição cíclica:requires
requires(!StdStreamable<T>)
requires(!is_std_streamable<T>::value)
error: satisfaction of constraint 'StdStreamable<T>' depends on itself
Entendo que usar a std::declval<std::ostream&>() << std::declval<const T&>()
expressão em uma requires
cláusula ao definir uma nova versão de operator<<
pode ser interpretado pelo compilador como uma dependência cíclica. Mas por que os conceitos do C++20 acionam esse problema de restrição cíclica, enquanto o SFINAE tradicional não? Esse comportamento é obrigatório pelo padrão, uma limitação conhecida de conceitos ou potencialmente um bug do compilador?
Exemplo mínimo reproduzível completo e detalhes adicionais:
Desde já, obrigado.