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.
Forse c'è un #pragma per disabilitare tali avvisi? [come qui?] (http://stackoverflow.com/questions/7159348/disable-single-warning-error) – Borgleader
@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 ... –
È possibile utilizzare anche l'invio di tag. – Jarod42