AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79529029
Accepted
Petr Filipský
Petr Filipský
Asked: 2025-03-23 21:39:40 +0800 CST2025-03-23 21:39:40 +0800 CST 2025-03-23 21:39:40 +0800 CST

Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

  • 772

Estou encontrando uma diferença inesperada no comportamento entre o SFINAE tradicional (usando type_traitsand 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 ( requiresclá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:requiresrequires(!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 requiresclá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:

  • https://godbolt.org/z/be7Yqxo93

Desde já, obrigado.

c++
  • 2 2 respostas
  • 1589 Views

2 respostas

  • Voted
  1. Best Answer
    Ahmed AEK
    2025-03-24T00:45:54+08:002025-03-24T00:45:54+08:00

    Esta é uma violação de ODR, seu programa está malformado, você está tentando fazer

    if (not_defined(X)) define(X)
    

    isso é proibido e a norma diz

    Se dois pontos diferentes de instanciação dão a uma especialização de modelo significados diferentes de acordo com a regra de uma definição, o programa está malformado e não é necessário diagnóstico .

    StdStreamable<T>ou is_std_streamable<T>daria significado diferente dependendo de onde fosse instanciado no programa.

    A SFINAE não foi obrigada a diagnosticar esse bug, porque falha de substituição não é um erro e, além disso, não requer diagnóstico , então eles simplesmente aceitaram esse bug.

    Os compiladores foram capazes de implementar conceitos de uma forma que detecta esse tipo de bug.


    você pode usar uma função livre que escolhe entre std::formatou object.printou ostream.operator<<dependendo de qual delas está definida, mas não pode definir uma delas se ela não existir usando metaprogramação de modelo.

    • 17
  2. Klaus
    2025-03-24T04:17:17+08:002025-03-24T04:17:17+08:00

    O problema com sobrecargas ambíguas vem das definições de funções-membro. std::ostream::operator<<()Seu conceito deve ser verificado apenas em relação às definições de funções-membro.

    Sua função de modelo autônoma genérica `operator<<()` precisa verificar esse conceito e não deve ter nenhuma especialização em relação às funções autônomas de modelo não genéricas do STL.

    Se a sobrecarga do operador usar um tipo especializado para o fluxo, será sempre uma correspondência melhor.

    Com isso em mente você deve escrever:

        template < typename T>
    concept HasShiftOut = requires ( T t )
    {
        std::declval<std::ostream>().operator<<(t);
    };
    
    template<typename _CharT, typename _Traits, typename T>
    inline std::basic_ostream<_CharT, _Traits>&
    operator<<(std::basic_ostream<_CharT, _Traits>& os, const T& t )
        requires (!HasShiftOut<T>)
    {
        os << "generic: ";
        t.print(os);
        os << std::endl;
        return os;
    }
    template<typename _CharT, typename _Traits>
    inline std::basic_ostream<_CharT, _Traits>&
    operator<<(std::basic_ostream<_CharT, _Traits>& os, const A& a )
    {
        return os << "custom A: " << a.a << std::endl;
    }
    std::ostream& operator<<(std::ostream& s, const B& b) { return s << "custom B: " << b.b << std::endl; }
    

    A função autônoma genérica não precisa ser restringida em relação às outras funções autônomas, pois elas já foram selecionadas ao selecionar a "melhor correspondência".

    Exemplo completo: https://godbolt.org/z/KKe46fsd1

    Mas é uma solução um pouco acadêmica, pois não faz muito sentido para um programa do mundo real, já que "todos" os outros tipos no seu programa devem fornecer print(). Neste caso, é muito mais fácil verificar o tipo para ter uma print()função. Não há função "genérica", pois você deve conhecer o interior de todos os tipos personalizados imprimíveis.

    • 3

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve