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 / 79560079
Accepted
Rabbitx
Rabbitx
Asked: 2025-04-07 21:58:58 +0800 CST2025-04-07 21:58:58 +0800 CST 2025-04-07 21:58:58 +0800 CST

Como funciona a inicialização de lista entre colchetes "class({args...})"?

  • 772

Tão confuso com a inicialização da lista cpp e a divergência do compilador.

Isso aconteceu enquanto eu estava revisando alguns recursos do cpp.

U ({ arg1, arg2, ... }) (11)

cppreference #copy-list-initialization case 11 diz

expressão de conversão funcional ou outras invocações de construtor, onde uma lista de inicializadores entre chaves é usada no lugar de um argumento do construtor. A inicialização da lista de cópias inicializa o parâmetro do construtor (observe que o tipo U neste exemplo não é o tipo que está sendo inicializado por lista; U's constructor's parameteré)

Então tentei o seguinte código para ver qual construtor o compilador Cpp escolherá:

#include <string>
#include <vector>
#include <map>
#include <tuple>
#include <iostream>
using namespace std;
struct A {
    static int id;
    A(int integer, float sci, string str, vector<int> vnum, pair<int, int> intP, tuple<int, string, float> isfTuple) {
    }
    A(pair<int, float> ifP) { // this also allows {} for default construction
    }
    A(const A &) = delete;
    A(A &&) = delete;
    ~A() {
        cout << "Destructed: " << ++id << endl;
    }
};

int main(){
    A b({1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}}); // works on MinGW
    // MinGW version: x86_64-14.2.0-release-win32-seh-ucrt-rt_v12-rev2
    // MSVC error: 'A::A(A &&)': attempting to reference a deleted function


    A partial({1, 1.0}); // works both on MinGW and MSVC with ISO C++20 Standard (/std:c++20)
}

No caso em questão A b(...), fiquei confuso sobre o motivo pelo qual o compilador funciona mesmo com o construtor de cópia A(const A &) = delete;e o construtor de movimento A(A&&)excluídos. Então, pesquisei sobre a conversão de construtores , a elisão de cópia e o rascunho de CPP em inicializadores, mas fiquei ainda mais confuso. Tentei compilar com MSVC e obtive o erro:

'A::A(A &&)': tentando referenciar uma função excluída

Então o Compiler Explorer obteve sucesso.

Então, o que está acontecendo? Bug no compilador MinGW ou MSVC? Ou este é um comportamento indefinido (mas o caso 11 parece estar bem definido)? Por que o MinGW e o Compiler Explorer tiveram sucesso?

Meus scripts de construção do GCC (usando vscode tasks.json):

g++ -Wall --std=gnu++20 -g ${file} -o ${fileDirname}\\${fileBasenameNoExtension}

Plataforma: Windows 11, é claro.

Conjunto de ferramentas do Visual Studio:Visual Studio 2022 (v143)

Padrão de linguagem do Visual Studio:ISO C++20 Standard (/std:c++20)

Resultado do Compiler Explorer


Editar:

Meus testes posteriores mostram que tanto o MSVC quanto o MinGW usam os mesmos construtores para cada caso se nenhum construtor for removido -- Result . O resultado do stdio permanece o mesmo. Há apenas uma divergência no comportamento de verificação do compilador (não sei qual é realmente) e o resultado da compilação é o mesmo, novamente, se nenhum construtor for removido. E sob -std=gnu++11, onde a eliminação de cópia não está disponível até o C++17, tanto o MSVC quanto o MinGW mantêm a mesma saída -- nenhum construtor de cópia/movimentação foi usado.

Então A b..., na verdade, o caso de uso 1 é uma sintaxe de inicialização direta, em vez do caso 11, o que faz a sintaxe do caso 1 entrar em conflito com o caso de teste, A b...ou ele está, na verdade, usando outra coisa?


Editar:

Parece que a elisão de cópia sempre acontece desde o C++ 11 durante a inicialização, mas algumas divergências acontecem no processamento de pré-valor na atribuição e na expressão de retorno entre compiladores. Acredito que a melhor prática é evitar aproveitar as vantagens mínimas do efeito colateral dos construtores.


Editar:

Acho que @Jarod42 está certo.

c++
  • 2 2 respostas
  • 122 Views

2 respostas

  • Voted
  1. Spencer Ingram
    2025-04-08T03:01:33+08:002025-04-08T03:01:33+08:00

    Para tornar seu código portátil entre GCC , Clang e MSVC , você pode forçar a inicialização direta usando inicialização uniforme com parênteses em vez de depender da inicialização por chaves em uma conversão funcional:

    Opção 1: Use parênteses e construa subobjetos manualmente

    int main() {
        A b(
            1,
            1.0f,
            "str"s,
            vector<int>{1, 2},
            pair<int, int>{1, 1},
            tuple<int, string, float>{1, "str", 1.0f}
        );
    }
    

    Isso evita a resolução ambígua {}e evita o fallback incorreto do MSVC para o construtor de movimento.

    Opção 2: Fornecer uma fábrica auxiliar do tipo agregado (se necessário)

    template<typename... Args>
    A make_A(Args&&... args) {
        return A(std::forward<Args>(args)...);
    }
    
    int main() {
        A b = make_A(
            1,
            1.0f,
            "str"s,
            vector<int>{1, 2},
            pair<int, int>{1, 1},
            tuple<int, string, float>{1, "str", 1.0f}
        );
    }
    

    Isso garante que os argumentos sejam passados ​​corretamente e que a elisão seja aplicada sem invocar construtores excluídos.

    Mas por que A b({ ... });funciona no GCC/MinGW, mas falha no MSVC?

    Esta linha:

    A b({1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}});

    é uma inicialização de lista de cópias. De acordo com C++20:

    • O compilador procura um construtor que Acorresponda a todos os valores dentro das chaves.

    • GCC e Clang resolvem isso corretamente para o construtor

      A(int, float, string, vector<int>, pair<int, int>, tuple<int, string, float>)

    • Como o objeto está sendo construído diretamente, a eliminação de cópia é garantida (desde C++17), portanto nenhum construtor de cópia ou movimentação é chamado, mesmo que sejam excluídos.

    Então o GCC aceita isso.


    Por que o MSVC rejeita isso?

    O MSVC parece tentar incorretamente criar um temporário Ae depois movê-lo para b. Como o construtor de movimentação foi excluído, isso resulta em um erro de compilação.

    Esse comportamento não está em conformidade com o padrão C++20, que exige a eliminação de cópias em cenários de inicialização direta como este. Portanto, trata-se de um problema de compilador no MSVC.


    Por que A partial({1, 1.0});funciona?

    A (pair<int, float>)

    Não é necessário nenhum construtor de movimentação ou cópia, por isso ele compila bem tanto no GCC quanto no MSVC.


    Resumo

    • GCC e Clang se comportam corretamente conforme C++20: eles executam inicialização direta com eliminação de cópia obrigatória.

    • O MSVC tenta usar incorretamente o construtor de movimento, apesar da elisão ser necessária.

    • Este é um problema do compilador no MSVC, não um comportamento indefinido.

    • 1
  2. Best Answer
    Jarod42
    2025-04-08T17:19:54+08:002025-04-08T17:19:54+08:00

    {..}não tem tipo.

    Em A partial({1, 1.0});,

    temos que procurar um construtor de sobrecarga de A, o melhor construtor viável é A(pair<int, float>)então {1, 1.0}usado para construir o par.

    Da mesma forma, para A b({1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}});, o melhor construtor viável é (o excluído ) A(A&&).

    Selecionar esse construtor parece tornar o programa malformado para clang/msvc, mesmo que ele seja eliminado com elisão de cópia de garantia (c++17).

    Fornecendo explicitamente Apara {..}(ou seja A b(A{1, 1, "str", {1, 2}, {1, 1}, {1, "str", 1}});) agradar a todos os compiladores Demo .

    • 0

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