Waarom worden constructorargumenten voor klassesjablonen niet automatisch bepaald?

Beschouw de volgende klasse:

template
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
}

Het volgende is niet toegestaan ​​in c ++:

auto p = Pair(10, 10);

Waarom is dat niet toegestaan? de typen kunnen volledig worden bepaald vanuit de aanroep van de constructor.
Ik weet dat er een oplossing voor is zoals:

template
Pair MakePair(const T1 &First, const T2 &Second)
{
    return Pair(First, Second);
}

Maar waarom is dat nodig? Waarom bepaalt de compiler niet alleen het type van de argumenten, net zoals het doet vanuit het functiesjabloon? Je zou het kunnen zeggen omdat de standaard het niet toestaat, dus waarom staat de standaard dat niet toe?

Edit:
Voor degenen die zeggen dat dit een voorbeeld is waarom dit niet mag worden toegestaan:

template
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

Ik kan dit precies doen met overladen van functiesjablonen:

template
Pair MakePair(const T1 &First, const T2 &Second)
{
    return Pair(First, Second);
}
template
Pair MakePair(const T2 &Second, const T1 &First)
{
    return Pair(First, Second);
}

Waarom is dit toegestaan ​​voor functies, maar niet voor klassen?

13
@GeneBushuyev: U geeft op dat u een constructor wilt door de klas te "bellen".
toegevoegd de auteur Dani, de bron
De twee MakePair -implementaties aan het einde zijn dubbelzinnig, u kunt ze schrijven, maar u moet de typen voor de functiesjabloon opgeven, omdat de compiler de beste overbelasting niet kan ophalen.
toegevoegd de auteur David Rodríguez - dribeas, de bron
Ik kan dit precies doen met overladen van functiesjablonen: Eigenlijk kan dat niet, om dezelfde redenen. Het is dubbelzinnig. codepad.org/axjCAZyL
toegevoegd de auteur Mooing Duck, de bron
@Mooing: ik denk dat hij zegt dat hij dezelfde dubbelzinnigheden kan creëren met functieoverbelasting als er sprake is van constructoroverbelasting, in welk geval u de sjabloonargumenten moet opgeven.
toegevoegd de auteur Benjamin Lindley, de bron
omdat constructeurs geen namen hebben en het onmogelijk is om ze te bellen, werkt argumentafleiding alleen voor functieaanroepen.
toegevoegd de auteur Gene Bushuyev, de bron

3 antwoord

Dit is wat Bjarne Stroustrup hierover te zeggen heeft:

Merk op dat class template-argumenten nooit worden afgeleid. De reden is   dat de flexibiliteit geboden door verschillende constructeurs voor een klasse   zou zo'n deductie in veel gevallen onmogelijk maken en in veel opzichten duister maken   meer.

10
toegevoegd
Hetzelfde kan worden gezegd over overladen van de functiesjabloon
toegevoegd de auteur Dani, de bron
Een voorbeeld toegevoegd in bewerking
toegevoegd de auteur Dani, de bron
Het verschil is dat er geen onduidelijkheid bestaat over het type dat wordt gemaakt door overbelasting van de functiesjabloon, omdat er geen type wordt gemaakt. Het bovenstaande is niet goed geformuleerd, maar ik hoop dat je mijn bedoeling begrijpt?
toegevoegd de auteur John Dibling, de bron
Lang geleden, toen auto voor het eerst werd geïntroduceerd, stelde ik voor om die syntaxis te gebruiken voor het afleiden van redenargumenten, bijvoorbeeld paar (10,10) . Dat kan worden gecombineerd met het gebruik van alias-declaratie: met mypair = pair zodat je mypair p (10,10); kunt schrijven in plaats van auto p = make_pair (10,10); . Die syntaxis zou helperfuncties make_something() (die geen ander doel dienen dan type-deductie) overbodig maken.
toegevoegd de auteur Gene Bushuyev, de bron

Stel je de volgende situatie voor.

template
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

Should p be a Pair or a Pair?

5
toegevoegd
@David: maar als het niet dubbelzinnig is, waarom is het dan niet toegestaan? U zei het omdat het misschien dubbelzinnig was, dus gaf ik een voorbeeld van functies die ook dubbelzinnig kunnen zijn, maar het is toegestaan ​​in functies in het algemene geval maar niet in klassen in het algemene geval.
toegevoegd de auteur Dani, de bron
Een voorbeeld toegevoegd in bewerking
toegevoegd de auteur Dani, de bron
Hetzelfde kan worden gedaan met overladen van de functiesjabloon.
toegevoegd de auteur Dani, de bron
@ Benjamin: Omdat het niet ondubbelzinnig is.
toegevoegd de auteur John Dibling, de bron
@Dani, over het algemeen vallen functies weg omdat de toegevoegde waarde de extra complexiteit in de compiler- of gebruikersprogramma's niet compenseert. Weet je (zonder een compiler te gebruiken) wat het type std :: min (10, 5.0) is?
toegevoegd de auteur David Rodríguez - dribeas, de bron
@Dani: het toegevoegde voorbeeld (de twee MakePair ) is dubbelzinnig. Terwijl je het kunt schrijven, kun je ze niet bellen zonder de typen handmatig in te voeren, en dat betekent op zijn beurt dat je terug bent bij stap één: de typen expliciet maken.
toegevoegd de auteur David Rodríguez - dribeas, de bron
@BenjaminLindley: in alle code op deze pagina tot nu toe komt Pair () overeen met twee verschillende functies. De typen zijn niet ambigu, de functies zijn.
toegevoegd de auteur Mooing Duck, de bron
Eh, oeps. Pair (10, 10.0) komt overeen met Pair :: Pair (const T1 & First, const T2 & Second) en Pair :: Pair (const T2 & Second, const T1 & First) . MakePair (10,10.0) komt overeen met MakePair (const T1 & First, const T2 & Second) en MakePair (const T2 & Second, const T1 & First) , dus in alle gevallen zijn de oproepen dubbelzinnig.
toegevoegd de auteur Mooing Duck, de bron
Oh, in de originele klas van OP is er geen onduidelijkheid. Ik kan geen reden bedenken die de taalontwerpers het verboden hebben. Het citaat van Bjarne Stroustrup is eigenlijk niet echt een reden.
toegevoegd de auteur Mooing Duck, de bron
@Dani: Ik zie je punt. Waarom kan het niet de soorten afleiden in gevallen waarin het ondubbelzinnig is, toch? Ik zal daarover moeten nadenken.
toegevoegd de auteur Benjamin Lindley, de bron
@John: Als er geen overbelasting is zoals die in de klas die ik heb getoond, en geen specialisaties, hoe is het dan dubbelzinnig?
toegevoegd de auteur Benjamin Lindley, de bron
IOW, wat anders zou Pair (10,10.0) betekenen naast Pair (10,10.0) ?
toegevoegd de auteur Benjamin Lindley, de bron
@Mooing Duck: ik volg je niet. In alle code op deze pagina voldoet Pair () tot niets, omdat noch mijn klasse noch Dani's een standaardconstructor heeft.
toegevoegd de auteur Benjamin Lindley, de bron
@Mooing: Maar ik verwijs naar de oorspronkelijke klasse van OP, waar er geen Pair :: Pair (const T2 & Second, const T1 & First) is, waar is de ambiguïteit daar?
toegevoegd de auteur Benjamin Lindley, de bron
@Dani: Hoe, precies?
toegevoegd de auteur Benjamin Lindley, de bron

Meer is over het algemeen niet beter. Wanneer u een systeem moet ontwerpen, moet u voorzichtig zijn met hoe de verschillende functies samenwerken en moet u kiezen voor de schoonste eenvoudigste oplossing die het meeste oplevert voor de gebruikers. De typische vragen beginnen met, wat biedt de functie de gebruikers , welke kosten zullen de functie vereisen en welke problemen de functie kan veroorzaken .

In dit geval helpt verstrekking van het type aftrek u erin over te slaan om het type te verstrekken bij het declareren van een variabele of bij het rechtstreeks aanroepen van een constructeur:

pair p = pair( 10, 20 );

De declaratie van de variabele kan gedaan worden met auto , dus het is niet langer een probleem, maar het zou wel eens de reden kunnen zijn voor deze functie. In het geval van de aanroep naar de constructor is de toegevoegde waarde vrij beperkt, u kunt een named constructor opgeven, zoals in de standaardbibliotheek:

auto p = make_pair( 10, 20 );

Dus aan het einde van de dag is de toegevoegde waarde van het direct kunnen bellen van de constructor eigenlijk beperkt, omdat je altijd een benoemde functie kunt bieden die het object voor je zal creëren. Merk op dat de mogelijke extra kopieën weg worden geoptimaliseerd, wat betekent dat er geen extra kosten zijn bij het aanroepen van make_pair versus het direct aanroepen van het constructor `paar (10,20)

Richt u nu op de negatieve implicaties die de functie kan hebben in gebruikerscode. De functie zou slechts in enkele gevallen kunnen worden gebruikt, waar er geen concurrerende overbelasting van de constructeur is, op dezelfde manier waarop deze de beste overbelasting vindt voor een functiesjabloon, maar wat nog belangrijker is specialisaties. De compiler zou alle mogelijke specialisaties van de klassemalplaatje moeten opzoeken en de reeks constructors moeten bepalen die elk van hen aanbiedt, en dan zou het de beste constructeuroverbelasting en de insluitende klassensjabloon moeten oplossen.

Terugkijkend op het specifieke voorbeeld dat u hebt opgegeven, zijn er geen specialisaties voor std :: pair , dus we zijn in het eenvoudige geval en toch zou de bovenstaande voorbeeldcode mislukken:

pair p = pair( 10, 20 );

Waarom? Nou, dit is eigenlijk het probleem, deze functie zou nogal wat verwarring bij de gebruikers brengen. Zoals ik al zei, kan de rechterkant van de uitdrukking eenvoudig worden opgelost met een eenvoudige overbelastingsresolutie (ervan uitgaande dat er geen specialisaties zijn), en het zou de argumenten afleiden tot int , beide . Dus de eerste stap zou de uitdrukking afleiden tot:

pair p = pair(10,20);

Now, that is equivalent to pair p( pair( 10, 20 ) ), and we need the second step of resolution, and at this point, the compiler will see that there is a templated constructor:

template 
struct pair {
   first_type first;
   second_type second;

   template 
   pair( U f, V s ) : first(f), second(s) {}
};

That means that every potential instantiation of std::pair has a perfect match for that constructor call, and the compiler cannot select, nor provide any sensible error message other than ambiguous in too many ways.

Now imagine coming to your favorite Q&A forum on the internet and guess how many questions would arise from this type of details. Ignoring the cost of implementing the feature, the result of having it in the language is that the language would be more complex and harder to write, it would impose an even steeper learning curve, and all that for basically no real value: in the cases where deduction can be applied, it is trivial to write a helper function that will provide the same advantages, like make_pair with no added cost.

Het feit dat u de typen moet leveren, neemt de complexiteit weg van de gebruiker: u hoeft niet na te denken over de vraag of de compiler het juiste type voor mij kan afleiden . Als u zich niet op deze details hoeft te concentreren, heeft u meer tijd om na te denken over het werkelijke probleem dat u wilt oplossen.

Soms is minder beter .

BTW, auto lost dit op de meest triviale manier op: het hoeft niet een constructor te vinden die overeenkomt met en het type ervan te raden, maar gebruik gewoon het type van de expressie aan de rechterkant, wat veel eenvoudiger is: werkt alleen met een enkel object en gebruikt dat type voor het nieuwe object. En zelfs dan, met de veel vereenvoudigde use case, werd het heen en weer besproken totdat een oplossing werd afgesproken, waarbij de details waren of deductie voor auto gebruikt kon worden om referenties te creëren, of > const of volatile zou deel uitmaken van het afgeleide type ...

Gewoon een gedachte: zonder een compiler te gebruiken, wat is het type expressie std: min (10, 5.0) ?

2
toegevoegd
Niet discussiëren met het algemene complexiteitspunt, maar het specifieke probleem "zie dat er een sjabloonsamenstelling is ... paar (U f, V s) " - de constructeurs zijn aangeroepen met een enkel ( paar ) argument dus zou de vergelijking niet eenduidig ​​het beste zijn met std :: pair (pair &&) (8) at cppreference ?
toegevoegd de auteur Tony Delroy, de bron
Je onderschat de last van het maken van helperfuncties, het synchroon houden ervan, en ook naamvervuiling. Scripts parser library schrijven Ik vond een groot aantal functies die helperfuncties, in feite heb ik veel _function.h-bestanden met alleen helpers. Namespace-vervuiling is ook een onaangenaam gevolg hiervan, omdat het gebruik van make_something meestal een slechte naamgeving is en vrijwel elke klasse via helper moet worden geïnstantieerd, de functies namen de relevante namen en de typen om botsingen te voorkomen moesten worden gewijzigd met prefix.
toegevoegd de auteur Gene Bushuyev, de bron