5

Il seguente codice mostra il nucleo di una ++ motivo template metaprogrammazione C ho utilizzato per determinare se un tipo T è un'istanza di una classe template specifica:Funzione con un argomento puntatore

#include <iostream> 

template<class A, class B> 
struct S{}; 

template<class A, class B> 
constexpr bool isS(const S<A,B>*) {return true;} 

template<class T> 
constexpr bool isS(const T*) {return false;} 

int main() { 
    S<int,char> s; 
    std::cout<<isS(&s)<<std::endl; 
    return 0; 
} 

E ' dispone di due sovraccarichi di un modello di funzione isS e produce 1, come previsto. Se rimuovere il puntatore dalla seconda isS, cioè sostituirlo con

template<class T> 
constexpr bool isS(const T) {return false;} 

il programma stampa inaspettatamente 0. Se entrambe le versioni di isS passano alla fase di risoluzione del sovraccarico della compilazione, l'output implica che il compilatore stia scegliendo il secondo sovraccarico. Ho testato questo sotto GCC, Clang e vC++ usando i compilatori online here, e tutti producono lo stesso risultato. Perché succede?

Ho letto più volte l'articolo di Herb Sutter e sembra che entrambe le funzioni isS siano considerate modelli di base. Se è così, allora è una questione di quale sia il più specializzato. Andando per intuizione e this answer, mi aspetto che il primo isS sia il più specializzato, perché T può corrispondere a ogni istanziazione di S<A,B>* e ci sono molte possibili istanze di T che non possono corrispondere a S<A,B>*. Mi piacerebbe individuare il paragrafo nella bozza di lavoro che definisce questo comportamento, ma non sono del tutto sicuro di quale fase della compilazione stia causando il problema. È qualcosa che ha a che fare con "14.8.2.4 Dedurre gli argomenti del modello durante l'ordinamento parziale"?

Questo problema è particolarmente sorprendente dato che il codice seguente, in cui il primo isS prende un riferimento const S<A,B> e la seconda prende un const T, emette il valore atteso 1:

#include <iostream> 

template<class A, class B> 
struct S{}; 

template<class A, class B> 
constexpr bool isS(const S<A,B>&) {return true;} 

template<class T> 
constexpr bool isS(const T) {return false;} 

int main() { 
    S<int,char> s; 
    std::cout<<isS(s)<<std::endl; 
    return 0; 
} 

Così il problema sembra essere qualcosa a che fare con come vengono trattati i puntatori.

+0

Un parametro 'const T' equivale a un parametro' T' (quindi otterrai una corrispondenza esatta) e hai bisogno di una conversione di qualifica da 'S *' a 'S const *'. Prova a usare 'S const s ;, invece nel tuo' main'. - Il test * più specializzato * si verifica molto tardi nella risoluzione di sovraccarico, se altri test non potrebbero decidere tra i sovraccarichi. Qui, possiamo selezionare il secondo in precedenza perché ha un rango migliore (corrispondenza esatta contro aggiustamento della qualifica). – dyp

+0

@dyp Ok, ma allora perché il compilatore sceglie il primo 'isS' nel frammento di codice in fondo, che comprende i riferimenti? Il compilatore non deve eseguire una conversione di qualifica da 'S &' a 'const S &' in questo caso? – Ose

+0

@Ose No, 'T *' per 'const T *' è una conversione di adattamento di qualifica, 'T' per' const T & 'è una conversione di identità come il riferimento * si collega direttamente * all'argomento. – TartanLlama

risposta

6

Poiché il secondo sovraccarico eliminerà il livello superiore const all'interno di const T, verrà risolto in T* durante la deduzione argomento. Il primo sovraccarico è una corrispondenza peggiore perché si risolverà a S<int, char> const*, che richiede una conversione di qualifica costante.

è necessario aggiungere const di fronte alla vostra variabile s in modo che il sovraccarico di più specializzato a calciare in:

#include <iostream> 

template<class A, class B> 
struct S {}; 

template<class A, class B> 
constexpr bool isS(const S<A,B>*) {return true;} 

//template<class T> 
//constexpr bool isS(const T*) {return false;} 

template<class T> 
constexpr bool isS(const T) {return false;} 

int main() { 
    S<int,char> const s{}; // add const here 
    std::cout<<isS(&s)<<std::endl; 
    return 0; 
} 

Live Example

Cambiando la prima di sovraccarico ad un const S<A,B>&, darà il corretto risultato perché c'è una conversione di identità invece di un aggiustamento di qualifica. vincolante

13.3.3.1.4 Guida [over.ics.ref]

1 Quando un parametro di tipo riferimento lega direttamente (8.5.3) a una espressione di argomento, la sequenza di conversione implicita è la conversione dell'identità, a meno che l'espressione dell'argomento abbia un tipo che è una classe derivata del tipo di parametro , nel qual caso la sequenza di conversione implicita è una derivata conversione di base (13.3.3.1).

Nota: in caso di dubbio su tali giochi argomento deduzione, è possibile utilizzare le __PRETTY_FUNCTION__ macro, che (su gcc/clang) vi darà più informazioni sui tipi desunti del modello selezionato. È quindi possibile commentare alcuni sovraccarichi per vedere come questo influenza la risoluzione di sovraccarico. Vedi questo live example.

+0

Grazie per questo, ma per favore vedi il mio commento sopra (sotto la domanda). – Ose

+0

@Ose tnx, aggiornato. – TemplateRex

+0

Grande, grazie per il riferimento allo standard! – Ose

3

La tua seconda versione non fornisce la risposta che ti aspetti perché la prima versione di isS richiede una conversione implicita, mentre il secondo no.

template<class A, class B> 
constexpr bool isS(const S<A,B>*); 

template<class T> 
constexpr bool isS(const T); 

S<int,char> s; 
isS(&s); 

noti che &s è di tipo S<int,char>*. Il primo isS sta cercando un const S<int,char>*, quindi il puntatore richiede una conversione. Il secondo isS è una corrispondenza diretta.


Se vi trovate a dover questo schema spesso, si potrebbe generalizzare, in questo modo:

template<template<typename...> class TT, typename T> 
struct is_specialization_of : std::false_type {}; 

template<template<typename...> class TT, typename... Ts> 
struct is_specialization_of<TT, TT<Ts...>> : std::true_type {}; 

Poi si controlla se un tipo è una specializzazione di S come questo:

is_specialization_of<S, decltype(s)>::value 
+0

Grazie per questo, ma per favore vedi il mio commento sopra (sotto la domanda). Inoltre, +1 per la generalizzazione: non avevo pensato di farlo! – Ose

+0

Ho notato due difetti della tua generalizzazione. Uno è che dice const TT non è una specializzazione di TT . Se ciò è auspicabile (come nel mio caso), è necessario aggiungere una seconda specializzazione per il const TT . L'altro difetto è che non può essere utilizzato con modelli di classi che hanno una combinazione di parametri di tipo e non di tipo (che è in realtà la maggior parte dei miei casi d'uso). Non vedo un modo per generalizzarlo ulteriormente per coprire questo caso ... – Ose

+0

Il primo problema può essere risolto avvolgendolo in un alias del modello che chiama 'std :: remove_const' sul tipo prima, o anche' std: : decadimento' per rimuovere tutto. Il secondo è un problema che ho incontrato prima e non ho ancora una buona soluzione per. – TartanLlama

Problemi correlati