decltype(auto) combineert het type deductiemechanisme van decltype met de voordelen van de auto syntaxis. Terwijl auto regels voor template argument deductie toepast die arrays naar pointers vervalsten en top-level cv-kwalifiers en referenties verwijderen, behoudt decltype(auto) het exacte type van de initializer expressie. Specifiek, als de expressie een ongehaakte variabelenaam is, levert decltype het verklaarde type; als het een gehaakte lvalue-expressie is, levert het een lvalue-referentie. Dit stelt functies in staat om hun retourwaarden perfect door te geven zonder expliciet decltype expressies op te geven of zich zorgen te maken over de complexiteit van referentieverlies.
We moesten een generieke wrapper implementeren voor een database access die conditioneel of een referentie naar een cached record of een nieuw geconstrueerde standaardwaarde retourneert. De cruciale vereiste was het behoud van de exacte semantiek van de retourtype—referenties moeten referenties blijven om het kopiëren van grote objecten te voorkomen, terwijl waarden moeten worden verplaatst of gekopieerd zoals gepast.
Een kandidaatoplossing maakte gebruik van een expliciete trailing return type met decltype en std::declval, waarbij decltype(std::declval<Accessor>()(key)) werd gespecificeerd. Voordelen: Het documenteert expliciet de type transformatie en werkt in C++11. Nadelen: De syntaxis is uitgebreid, vereist perfect doorgeven van argumenten naar std::declval, en wordt ononderhoudbaar bij het omgaan met meerdere overloads of conditionele logica.
Een andere benadering gebruikte gewone auto als retourtype, met de veronderstelling dat de compiler het geschikte type zou afleiden. Voordelen: Het is beknopt en leesbaar. Nadelen: Auto past vervalregels toe, waardoor Record& wordt omgezet naar Record en const-kwalifiers worden verwijderd, wat onnodige diepe kopieën veroorzaakt en de const-correctheid schendt wanneer de aanroeper een alleen-lezen referentie verwacht.
We kozen voor decltype(auto) als retourtype, wat de typebehoudregels van decltype toepast op de geretourneerde expressie. Deze keuze elimineerde boilerplate terwijl het garandeerde dat lvalue-referenties, const-kwalifiers, en rvalue-referenties correct naar de aanroeper worden doorgestuurd. Het resultaat was een nul-overhead generieke facade die zowel waarde- als referentietoevoegingen zonder code duplicatie of impliciete conversies afhandelt, wat de latentie bij frequente cache-opzoeken vermindert.
Waarom levert decltype((var)) een lvalue-referentietype op, terwijl decltype(var) het verklaarde type oplevert, en hoe beïnvloedt dit decltype(auto) retourverklaringen?
decltype is onderworpen aan twee verschillende regels: voor een ongehaakte id-expressie (zoals var), levert het het type op dat voor die entiteit is verklaard; voor elke andere expressie, inclusief gehaakte expressies zoals (var), levert het het type van die expressie op, wat een lvalue-referentietype is als de expressie een lvalue is. Bij het gebruik van decltype(auto), creëert het retourneren van (var) een referentie naar een lokale variabele, wat leidt tot dangling referenties bij het verlaat van de functie. Daarom moet men onnodige haakjes in retourverklaringen vermijden bij het gebruik van decltype(auto), aangezien de extra haakjes de expressiecategorie van een id-expressie naar een lvalue-expressie veranderen.
Hoe interageert decltype(auto) met xvalues (vervluchten waarden) in vergelijking met prvalues?
decltype(auto) behoudt waarde categorieën nauwkeurig volgens decltype semantiek. Als een functie een xvalue retourneert (bijv. std::move(obj)), deduceert decltype(auto) het type als een rvalue-referentie (T&&), terwijl auto het type zou deduceren als T. Dit onderscheid is cruciaal bij het implementeren van perfect-forwarding factory functies die de verplaatsingssemantiek van geretourneerde temporaries moeten behouden zonder kopieën af te dwingen of expliciete std::move annotaties op de aanroeplocatie te vereisen.
Wat gebeurt er wanneer decltype(auto) wordt gebruikt met gehaakte initializer lijsten, en waarom verschilt het van auto deductie?
Wanneer geïnitieerd met een gehaakte-init-lijst zoals {1, 2, 3}, deduceert auto std::initializer_list<int>, maar decltype(auto) probeert de gehaakte-init-lijst zelf als een type te deduceren, wat een niet-gededuceerde context is voor decltype en resulteert in slecht gevormde code. Dit voorkomt dat decltype(auto) wordt gebruikt om gehaakte initializer lijsten rechtstreeks te retourneren, in tegenstelling tot auto, dat de std::initializer_list tijdelijke kan deduceren. Dit subtiele verschil ontstaat omdat decltype het expressietype exact behoudt, inclusief niet-gededuceerde contexten waar de expressie geen variabele of functie-aanroep is.