2014-10-31 6 views
14

Si consideri il codice:Come evitare l'espressione "l'espressione condizionale è costante" con condizioni costanti in fase di compilazione nel codice del modello?

template <typename T> 
CByteArray serialize(const T& value) 
{ 
    if (std::is_pod<T>::value) 
     return serializePodType(value); 
    else if (std::is_convertible<T, Variant>::value) 
     return serialize(Variant(value)); 
    else 
    { 
     assert(0 == "Unsupported type"); 
     return CByteArray(); 
    } 
} 

Ovviamente, il compilatore è giusto per darmi questo avvertimento per if (std::is_pod<T>::value) ecc, ma come faccio a evitare questo? Non riesco a trovare un modo per evitare questo controllo e non ci sono ancora static if in C++ (ancora).

È possibile utilizzare il principio SFINAE per evitare questo if?

+0

Forse c'è un #pragma per disabilitare tali avvisi? [come qui?] (http://stackoverflow.com/questions/7159348/disable-single-warning-error) – Borgleader

+0

@Borgleader: certo, ma non è un'opzione per me poiché questo codice verrà compilato per Windows con MSVC, per Linux e Android con GCC, per Mac e iOS con clang ... –

+1

È possibile utilizzare anche l'invio di tag. – Jarod42

risposta

10

È possibile utilizzare il principio SFINAE per evitare questo se?

Sì, almeno per i casi non predefiniti:

template <typename T> 
typename std::enable_if<std::is_pod<T>::value, CByteArray>::type 
serialize(const T& value) 
{ 
    return serializePodType(value); 
} 

template <typename T> 
typename std::enable_if< 
    !std::is_pod<T>::value && // needed if POD types can be converted to Variant 
    std::is_convertible<T, Variant>::value, CByteArray>::type 
serialize(const T& value) 
{ 
    return serialize(Variant(value)); 
} 

Se si desidera un run-time, invece di compilazione, di errore per i tipi non supportati, quindi dichiarare una funzione variadic per la cattura qualsiasi argomento che non corrisponde agli altri overload.

CByteArray serialize(...) 
{ 
    hlassert_unconditional("Unsupported type"); 
    return CByteArray(); 
} 
+0

In realtà voglio un errore in fase di compilazione (chi non ama il rilevamento precoce degli errori?), E userei 'static_if' invece di' assert', se C++ avesse tale costrutto. –

+0

@VioletGiraffe: In tal caso, basta lasciare il caso predefinito e si otterrà un errore in fase di compilazione. C'è 'static_assert' come un altro modo per causare condizionalmente un errore in fase di compilazione; ma non ne hai bisogno qui. –

+0

@MikeSeymour L'ultimo sovraccarico non ha bisogno di SFINAE ... si può semplicemente fare 'CByteArray serialize (...) {assert(); return CByteArray(); } '. Sarà preso solo se ogni altro sovraccarico fallisce. – Barry

5

Sarei tentato di lasciarlo così com'è, francamente. Il compilatore sta dimostrando di sapere che le filiali non utilizzate possono essere ottimizzate. Certo, l'avviso è un po 'trascinante, ma ..

In ogni caso, se si vuole veramente farlo, usare std::enable_if sul tipo di ritorno della funzione.

+0

Siamo spiacenti, ho letto la descrizione di 'enable_if' ma non capisco come si propone di usarlo in questo caso. –

5

Di cosa si tratta? http://ideone.com/WgKAju

#include <cassert> 
#include <type_traits> 
#include <iostream> 

class CByteArray { public: CByteArray() {}}; 

class Variant {}; 

template<typename T> 
CByteArray serializePodType(const T&) 
{ 
    printf("serializePodType\n"); 
    return CByteArray(); 
} 

CByteArray serializeVariant(const Variant& v) 
{ 
    printf("serializeVariant\n"); 
    return CByteArray(); 
} 

template <typename T> 
typename std::enable_if<std::is_pod<T>::value, CByteArray>::type serialize(const T& value) 
{ 
    return serializePodType(value); 
} 

template <typename T> 
typename std::enable_if<std::is_convertible<T, Variant>::value && !std::is_pod<T>::value, CByteArray>::type serialize(const T& value) 
{ 
    return serializeVariant(Variant(value)); 
} 

class ConvertibleToVariant : public Variant { virtual void foo(); }; 

struct POD {}; 

struct NonPOD { virtual void foo(); }; 

int main() 
{ 
    POD pod; 
    ConvertibleToVariant ctv; 
    //NonPOD nonpod; 

    const auto ctv_serialised = serialize(ctv); 
    const auto pod_serialised = serialize(pod); 
    //const auto nonpod_serialised = serialize(nonpod); 
} 

Questa documentazione è molto bello per enable_if: http://en.cppreference.com/w/cpp/types/enable_if

+0

Dannazione, Mike mi ha battuto per circa 8 minuti. Questo è quello che ottengo per postare le risposte dopo un giro in un pub all'ora di pranzo. –

+1

Grazie comunque per lo sforzo, prendi una consolazione positiva :) –

+0

Haha, grazie! :) –

6

Si può usare qualcosa come:

template <typename T> CByteArray serialize(const T& value); 

namespace detail 
{ 
    template <typename T> 
    CByteArray serializePod(const T& value, std::true_type); 
    { 
     return serializePodType(value); 
    } 

    template <typename T> 
    CByteArray serializePod(const T& value, std::false_type); 
    { 
     static_assert(std::is_convertible<T, Variant>::value, "unexpect type"); 
     return serialize(Variant(value)); 
    } 
} 

template <typename T> 
CByteArray serialize(const T& value) 
{ 
    return detail::serializePod(value, std::is_pod<T>{}); 
} 
2

Ora, è possibile utilizzare i vincoli di modello per risolvere il problema, mi piace usare un piccola macro per contribuire a rendere più semplice il testo enable_if:

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0 

Poi si potrebbe definirli direttamente nella funzione:

template <typename T, REQUIRES(std::is_pod<T>::value)> 
CByteArray serialize(const T& value) 
{ 
    return serializePodType(value); 
} 

template <typename T, REQUIRES(
    !std::is_pod<T>::value && 
    !std::is_convertible<T, Variant>::value 
)> 
CByteArray serialize(const T& value) 
{ 
    assert(0 == "Unsupported type"); 
    return CByteArray(); 
} 

// This is put last so `serialize` will call the other overloads 
template <typename T, REQUIRES(
    !std::is_pod<T>::value && 
    std::is_convertible<T, Variant>::value 
)> 
CByteArray serialize(const T& value) 
{ 
    return serialize(Variant(value)); 
} 

Tuttavia, questo diventa brutto molto rapidamente. Per prima cosa, devi annullare le altre condizioni per evitare ambiguità. In secondo luogo, le funzioni devono essere ordinate in modo che le altre funzioni siano dichiarate o definite prima di essere chiamate in modo ricorsivo. Non scala veramente bene. Se è necessario aggiungere ulteriori condizioni in futuro, può diventare molto più complicato.

Una soluzione migliore è utilizzare conditional overloading con un fix point combinator. La libreria Fit fornisce un adattatore conditional e fix, quindi non è necessario scrivere il proprio.Quindi, in C++ 14, si potrebbe scrivere:

const constexpr serialize = fit::fix(fit::conditional(
    FIT_STATIC_LAMBDA(auto, const auto& value, 
     REQUIRES(std::is_pod<decltype(value)>())) 
    { 
     return serializePodType(value); 
    }, 
    FIT_STATIC_LAMBDA(auto self, const auto& value, 
     REQUIRES(std::is_convertible<decltype(value), Variant>())) 
    { 
     return self(Variant(value)); 
    }, 
    FIT_STATIC_LAMBDA(auto, const auto&) 
    { 
     assert(0 == "Unsupported type"); 
     return CByteArray(); 
    } 
)); 

Tuttavia, se non si sta utilizzando C++ 14 ancora, si dovrà scriverle come oggetti funzione invece:

struct serialize_pod 
{ 
    template<class Self, class T, 
     REQUIRES(std::is_pod<T>::value)> 
    CByteArray operator()(Self, const T& value) const 
    { 
     return serializePodType(value); 
    } 
}; 

struct serialize_variant 
{ 
    template<class Self, class T, 
     REQUIRES(std::is_convertible<T, Variant>::value)> 
    CByteArray operator()(Self self, const T& value) const 
    { 
     return self(Variant(value)); 
    } 
}; 

struct serialize_else 
{ 
    template<class Self, class T> 
    CByteArray operator()(Self, const T&) const 
    { 
     assert(0 == "Unsupported type"); 
     return CByteArray(); 
    } 
}; 

const constexpr fit::conditional_adaptor<serialize_pod, serialize_variant, serialize_else> serialize = {}; 

Infine, per il tuo caso specifico, puoi abbandonare la parte else a meno che tu non abbia realmente bisogno di un controllo di runtime. Poi si può solo avere le due overload:

const constexpr serialize = fit::fix(fit::conditional(
    FIT_STATIC_LAMBDA(auto, const auto& value, 
     REQUIRES(std::is_pod<decltype(value)>())) 
    { 
     return serializePodType(value); 
    }, 
    FIT_STATIC_LAMBDA(auto self, const auto& value, 
     REQUIRES(std::is_convertible<decltype(value), Variant>())) 
    { 
     return self(Variant(value)); 
    } 
)); 

in modo da avere un errore di compilazione, invece. La cosa bella dell'uso di enable_if e dei vincoli è che l'errore sarà nel codice utente invece che nel codice (con un lungo backtrace). Ciò aiuta a chiarire che l'utente è l'unico a fare l'errore invece di un problema con il codice della libreria.

+0

Fantastico trucco, il primo. Il resto è troppo complicato, secondo me. –

Problemi correlati