2015-06-01 12 views
13

Perché il seguente codice viene compilato in clang ++?Impedisci la conversione da uint64_t a uint16_t

Ci sono dei flag C++ per impedire che ciò accada: vorrei che il compilatore generasse un errore perché sto passando uno std :: uint64_t come argomento a una funzione che accetta std :: uint16_t.

#include <cstdint> 
using namespace std; 

void foo(uint16_t x) { 
} 

int main() { 
    uint64_t x = 10000; 
    foo(x); 
    return 0; 
} 
+2

Perché shouldn' vero? –

+3

Le conversioni di restringimento sono consentite in C++ e le conversioni di restringimento senza segno sono ben definite –

+12

L'aggiunta di '-Werror = conversion' farà sì che il tuo esempio non venga compilato. – Praetorian

risposta

33

è possibile eliminare una funzione in C++ 11

void foo(uint64_t) = delete; 

funziona aggiungendo la firma a una risoluzione di sovraccarico di funzione, e se è stata una partita migliore, si verifica un errore. È inoltre possibile rendere più generica per consentire solo si firma originale

template <class T> void foo(T&&) = delete; 
+0

Come si risponde alla domanda? –

+5

ha chiesto come impedire che ciò accada e questo lo impedirà efficacemente. – Puppy

+2

@JohnBollinger: Il programma specificato non riuscirà a compilare con questo aggiunto, non sarebbe? – Guvante

9

È inoltre possibile utilizzare enable_if come parametro di ritorno SFINAE

#include <iostream> 
#include <cstdint> 
#include <type_traits> 

template<typename T> 
typename std::enable_if<std::is_same<T, uint16_t>::value>::type 
foo(T x) 
{ 
    std::cout << "uint16_t" << std::endl; 
} 

template<typename T> 
typename std::enable_if<!std::is_same<T, uint16_t>::value>::type 
foo(T x) 
{ 
    std::cout << "rest" << std::endl; 
} 

int main() { 
    uint16_t x = 10000; 
    uint64_t y = 100000; 
    foo(x); // picks up uint16_t version 
    foo(y); // picks up anything else, but NOT uint16_t 
    return 0; 
} 

In questo modo si può avere uno di sovraccarico che si occupa specificamente con uint16_t, e un altro sovraccarico che si occupa di qualsiasi altra cosa.

+0

Questo è buono ma impedisce anche le conversioni in ampliamento (come l'altra risposta). Sarebbe bello se la funzione potesse accettare valori di 'uint8_t' per esempio, e forse anche letterali il cui valore si adatta a' uint16_t'. Qualcuno ha suggerito di usare 'std :: common_type' [qui] (http://stackoverflow.com/a/16945743/1505939) tuttavia non penso che funzionerebbe per' uint16_t' a causa della promozione di interi a 'int' –

+0

@ MattMcNabb hmm, questo sembra un po 'più complicato, ed è in realtà interessante: come accettare tutto ciò che "si adatta" al parametro. Bene, c'è un "modo": forza bruta, specificare esplicitamente il modello, come 'pippo (x);'. Non molto carino però. – vsoftco

5

Se si desidera consentire l'ampliamento conversioni, ma vietare le conversioni restringimento, forse:

void foo(uint16_t x) { 
} 

template <class T> 
void foo(const T&& t) 
{ 
    return foo(uint16_t{t}); 
} 

Questo costringe tutti i tipi ad eccezione uint16_t stesso di passare attraverso la lista-inizializzazione, che vieta restringimento conversioni.

Non funziona così bene se hai già un numero di sovraccarichi, però.

+0

Purtroppo il restringimento non è richiesto per provocare un errore, vedere ad es. http://stackoverflow.com/q/28466069/3093378. Ad esempio, questo funziona: http://ideone.com/DHqBYL. Forse mi manca qualcosa. – vsoftco

+0

@vsoftco "se è necessaria una conversione restringente (vedi sotto) per convertire l'elemento in' T', il programma è mal formato ", da 8.5.4 – Barry

+0

@Barry sì, è mal formato, tuttavia il compilatore non è necessario per emettere un errore. E in effetti compila il programma. – vsoftco

6

Ecco una soluzione che permettesse l'ampliamento conversioni e prevenire quelli restringimento:

#include <cstdint> 
#include <type_traits> 

void foo(uint16_t x) { 
} 

template <class T> 
typename std::enable_if<sizeof(uint16_t) < sizeof(T)>::type foo(const T& t) = delete; 

int main() { 
    uint64_t x = 10000; 
    uint16_t y = 10000; 
    uint8_t z = 100; 
    // foo(x); // ERROR: narrowing conversion 
    foo(y); // OK: no conversion 
    foo(z); // OK: widening conversion 
    return 0; 
} 

Nel caso in cui si desideri anche per non consentire le chiamate con argomenti di tipo firmati (conversioni tra tipi firmati e non firmati non sono " lossless "), è possibile utilizzare la seguente dichiarazione, invece:

#include <cstdint> 
#include <type_traits> 

void foo(uint16_t x) { 
} 

template <class T> 
typename std::enable_if<(sizeof(uint16_t) < sizeof(T)) || 
         (std::is_signed<T>::value != std::is_signed<uint16_t>::value) 
         >::type 
foo(const T& t) = delete; 

int main() { 
    uint64_t u64 = 10000; 
    uint16_t u16 = 10000; 
    uint8_t u8 = 100; 
    int64_t s64 = 10000; 
    int16_t s16 = 10000; 
    int8_t s8 = 100; 

    //foo(u64); // ERROR: narrowing conversion 
    foo(u16); // OK: no conversion 
    foo(u8); // OK: widening conversion 
    //foo(s64); // ERROR: narrowing conversion AND signed/unsigned mismatch 
    //foo(s16); // ERROR: signed/unsigned mismatch 
    //foo(s8); // ERROR: signed/unsigned mismatch 

    return 0; 
} 
3

Anche se la maggior parte delle risposte qui sono tecnicamente corretti, è molto probabile che non vuole il comportamento di applicare solo a questa funzione, quindi una 'soluzione livello di codice' che devi wr questo per ognuno di questi casi di conversione non è probabilmente quello che vuoi.

Su un "livello di progetto/compilation" è possibile aggiungere questo flag mettere in guardia su queste conversioni:

-Wconversion 

o se preferite direttamente trattarli come errori:

-Werror=conversion 
+1

Si noti che questo non avverrà (testato in GCC 4.9.2) sulla conversione da 'int16_t' firmato a 'uint16_t' senza segno, che può anche causare il passaggio di un valore non valido nella funzione. È possibile aggiungere '-Wsign-conversion' (o' -Werror = sign-conversion') per rilevare le modifiche nella firma. –