Classe enable_if
Crea un'istanza di un tipo in modo condizionale per la risoluzione dell'overload SFINAE. Il typedef annidato enable_if<Condition,Type>::type
esiste, ed è sinonimo di Type
, solo ed esclusivamente se Condition
è true
.
Sintassi
template <bool B, class T = void>
struct enable_if;
Parametri
B
Valore che determina l'esistenza del tipo risultante.
T
Tipo di cui creare un'istanza se B
è true
.
Osservazioni:
Se B
è true
, enable_if<B, T>
ha un typedef annidato denominato "type" sinonimo di T
.
Se B
è false
, enable_if<B, T>
non ha un typedef annidato denominato "type".
Viene fornito questo modello di alias:
template <bool B, class T = void>
using enable_if_t = typename enable_if<B,T>::type;
In C++, l'errore di sostituzione dei parametri del modello non è un errore in sé. Questo errore viene definito SFINAE (l'errore di sostituzione non è un errore). In genere enable_if
viene usato per rimuovere candidati dalla risoluzione dell'overload, ovvero seleziona il set di overload, in modo da consentire il rifiuto di una definizione a favore di un'altra. Questo è conforme al comportamento di SFINAE. Per altre informazioni su SFINAE, vedere L'errore di sostituzione non è un errore su Wikipedia.
Ecco quattro scenari di esempio:
- Scenario 1: wrapping del tipo restituito di una funzione:
template <your_stuff>
typename enable_if<your_condition, your_return_type>::type
yourfunction(args) {// ...
}
// The alias template makes it more concise:
template <your_stuff>
enable_if_t<your_condition, your_return_type>
yourfunction(args) {// ...
}
- Scenario 2: aggiunta di un parametro di funzione che include un argomento predefinito:
template <your_stuff>
your_return_type_if_present
yourfunction(args, enable_if_t<your condition, FOO> = BAR) {// ...
}
- Scenario 3: aggiunta di un parametro di modello che include un argomento predefinito:
template <your_stuff, typename Dummy = enable_if_t<your_condition>>
rest_of_function_declaration_goes_here
- Scenario 4: se la funzione include un argomento non basato su modello, è possibile eseguire il wrapping del relativo tipo:
template <typename T>
void your_function(const T& t,
enable_if_t<is_something<T>::value, const string&>
s) {// ...
}
Lo scenario 1 non funziona con i costruttore e gli operatori di conversione poiché questi non dispongono di tipi restituiti.
Lo scenario 2 lascia il parametro non denominato. È possibile specificare ::type Dummy = BAR
, ma il nome Dummy
è irrilevante ed è probabile che l'assegnazione di un nome generi un avviso di tipo "parametro senza riferimento". È necessario scegliere un tipo di parametro di funzione FOO
e l'argomento BAR
predefinito. È possibile specificare int
e 0
, ma gli utenti del codice potrebbero accidentalmente passare un intero supplementare alla funzione, che verrebbe ignorato. È invece consigliabile usare void **
e 0
o nullptr
perché quasi nulla è convertibile in void **
:
template <your_stuff>
your_return_type_if_present
yourfunction(args, typename enable_if<your_condition, void **>::type = nullptr) {// ...
}
Lo scenario 2 funziona anche per i costruttori normali. Non è invece adatto agli operatori di conversione, che non accettano parametri supplementari. Non funziona anche per variadic
i costruttori perché l'aggiunta di parametri aggiuntivi rende il pacchetto di parametri della funzione un contesto non dedotto e quindi sconfigge lo scopo di enable_if
.
Lo scenario 3 usa il nome Dummy
, ma è facoltativo. In realtà funziona anche solo "typename = typename
", ma se lo si ritiene strano, si può usare un nome "fittizio" qualsiasi, purché non sia un nome che potrebbe essere usato nella definizione di funzione. Se non si assegna un tipo a enable_if
, l'impostazione predefinita è void, un risultato del tutto ragionevole poiché Dummy
non è rilevante. Questo funziona per tutti gli elementi, inclusi operatori di conversione e variadic
costruttori.
Lo scenario 4 funziona per i costruttori che dispongono di tipi restituiti e quindi risolve la limitazione di wrapping dello scenario 1. Lo scenario 4, tuttavia, è limitato agli argomenti di funzione non basati su modello, che non sempre sono disponibili (l'uso dello scenario 4 su un argomento di funzione basato su modello impedisce il funzionamento della deduzione di argomenti del modello su di esso).
L'oggetto enable_if
è potente, ma è anche pericoloso se usato in modo improprio. Dato che il suo scopo consiste nell'eliminazione di candidati prima della risoluzione dell'overload, se viene usato in modo improprio i suoi effetti possono essere molto ambigui. Di seguito sono elencati alcuni suggerimenti:
Non usare
enable_if
per selezionare tra implementazioni in fase di compilazione. Non scrivere mai unenable_if
perCONDITION
e un altro per!CONDITION
. Usare invece un criterio di invio tag, ad esempio un algoritmo che seleziona le implementazioni in base ai punti di forza degli iteratori assegnati.Non usare
enable_if
per applicare i requisiti. Se si desidera convalidare i parametri del modello e se la convalida non riesce, generare un errore anziché selezionare un'altra implementazione, usarestatic_assert
.Usare
enable_if
quando è presente un set di overload che rende ambiguo un codice altrimenti corretto. Spesso questo accade nei costruttori a conversione implicita.
Esempio
In questo esempio viene illustrato come la funzione del enable_if
modello della libreria std::make_pair()
standard C++ sfrutta .
void func(const pair<int, int>&);
void func(const pair<string, string>&);
func(make_pair("foo", "bar"));
In questo esempio make_pair("foo", "bar")
restituisce pair<const char *, const char *>
. La risoluzione dell'overload deve stabilire quale func()
è richiesto. pair<A, B>
ha un costruttore a conversione implicita tratto da pair<X, Y>
. Non si tratta di una novità. Era infatti presente in C++98. In C++98/03, tuttavia, la firma del costruttore a conversione implicita esiste sempre, anche se è pair<int, int>(const pair<const char *, const char *>&)
. Per la risoluzione dell'overload non è rilevante che un tentativo di creare un'istanza del costruttore determini una forte esplosione perché const char *
non è implicitamente convertibile in int
; vengono cercate solo le firme, prima della creazione di un'istanza delle definizioni di funzione. Il codice di esempio è quindi ambiguo perché sono presenti firme per convertire pair<const char *, const char *>
sia in pair<int, int>
che in pair<string, string>
.
C++11 risolve questa ambiguità usando enable_if
per verificare che pair<A, B>(const pair<X, Y>&)
esista solo quando const X&
è convertibile implicitamente in A
e const Y&
è convertibile implicitamente in B
. Ciò consente alla risoluzione dell'overload di determinare che pair<const char *, const char *>
non è convertibile in pair<int, int>
e che l'overload che accetta pair<string, string>
è fattibile.
Requisiti
Intestazione: <type_traits>
Spazio dei nomi: std