Perché {} come argomento della funzione non porta all'ambiguità?


20

Considera questo codice:

#include <vector>
#include <iostream>

enum class A
{
  X, Y
};

struct Test
{
  Test(const std::vector<double>&, const std::vector<int>& = {}, A = A::X)
  { std::cout << "vector overload" << std::endl; }

  Test(const std::vector<double>&, int, A = A::X)
  { std::cout << "int overload" << std::endl; }
};

int main()
{
  std::vector<double> v;
  Test t1(v);
  Test t2(v, {}, A::X);
}

https://godbolt.org/z/Gc_w8i

Questo stampa:

vector overload
int overload

Perché questo non produce un errore di compilazione a causa della risoluzione ambigua del sovraccarico? Se il secondo costruttore viene rimosso, otteniamo vector overloaddue volte. Come / secondo quale metrica è intuna corrispondenza inequivocabilmente migliore {}di std::vector<int>?

La firma del costruttore può sicuramente essere ulteriormente ritagliata, ma sono appena stato ingannato da un codice equivalente e voglio assicurarmi che nulla di importante venga perso per questa domanda.


Se ricordo correttamente {}come un blocco di codice, assegna 0 alle variabili - esempio: const char x = {}; è impostato su 0 (null char), lo stesso per int ecc.
Seti

2
@Seti Questo è ciò che {}effettivamente fa in alcuni casi speciali, ma non è generalmente corretto (per cominciare, std::vector<int> x = {};funziona, std::vector <int> x = 0;non lo fa). Non è semplice come " {}assegna zero".
Max Langhof,

Giusto, non è così semplice, ma assegna ancora zero - penso che penso che questo segnale sia abbastanza confuso e non dovrebbe essere usato davvero
Seti

2
@Seti struct A { int x = 5; }; A a = {};non assegna zero in alcun senso, costruisce un Acon a.x = 5. Questo è diverso A a = { 0 };, che viene inizializzato a.xsu 0. Lo zero non è inerente a {}, è inerente al modo in cui ogni tipo è costruito di default o inizializzato dal valore. Vedi qui , qui e qui .
Max Langhof,

Continuo a pensare che il valore costruito per impostazione predefinita sia confuso (richiede di controllare il comportamento o mantenere molte conoscenze in ogni momento)
Seti

Risposte:


12

È in [over.ics.list] , enfatizza il mio

6 In caso contrario, se il parametro è una classe X non aggregata e una risoluzione di sovraccarico per [over.match.list] sceglie un singolo costruttore C di X migliore per eseguire l'inizializzazione di un oggetto di tipo X dall'elenco di inizializzatori dell'argomento:

  • Se C non è un costruttore di elenchi di inizializzatori e l'elenco di inizializzatori ha un singolo elemento di tipo cv U, dove U è X o una classe derivata da X, la sequenza di conversione implicita ha il rango di corrispondenza esatta se U è X o il rango di conversione se U è derivato da X.

  • Altrimenti, la sequenza di conversione implicita è una sequenza di conversione definita dall'utente con la seconda sequenza di conversione standard una conversione di identità.

9 Altrimenti, se il tipo di parametro non è una classe:

  • [...]

  • se l'elenco di inizializzatori non ha elementi, la sequenza di conversione implicita è la conversione dell'identità. [ Esempio:

    void f(int);
    f( { } ); // OK: identity conversion
    

    fine esempio]

La std::vectorviene inizializzato dal costruttore e il proiettile in Deems grassetto un definito dall'utente converison. Nel frattempo, per an int, questa è la conversione dell'identità, quindi supera il rango del primo c'tor.


Sì, sembra accurato.
Columbo

Interessante vedere che questa situazione è esplicitamente considerata nella norma. Mi sarei davvero aspettato che fosse ambiguo (e sembra che avrebbe potuto essere facilmente specificato in quel modo). Non riesco a seguire il ragionamento nella tua ultima frase - 0ha il tipo intma non il tipo std::vector<int>, come fa un "come se" scritto alla natura "non tipizzata" di {}?
Max Langhof

@MaxLanghof - L'altro modo di vederlo è che per i tipi non di classe, non è una conversione definita dall'utente in alcun modo. Al contrario è un inizializzatore diretto per il valore predefinito. Da qui un'identità in questo caso.
StoryTeller - Unslander Monica

Quella parte è chiara. Sono sorpreso dalla necessità di una conversione definita dall'utente in std::vector<int>. Come hai detto, mi aspettavo che "il tipo di parametro finisca per decidere qual è il tipo di argomento", e un {}"tipo" (per così dire) std::vector<int>non dovrebbe necessitare di una conversione (non identitaria) per inizializzare a std::vector<int>. Lo standard ovviamente dice che lo fa, quindi è quello, ma per me non ha senso. (Intendiamoci, non sto sostenendo che tu o lo standard avete torto, sto solo cercando di conciliare questo con i miei modelli mentali.)
Max Langhof

Ok, quella modifica non era la risoluzione che speravo, ma abbastanza giusta. : D Grazie per il tuo tempo!
Max Langhof
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.