Как избежать "условного выражения является постоянным" предупреждением с условием постоянной компиляции в коде шаблона?
Рассмотрим код:
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();
}
}
Очевидно, что компилятор прав, чтобы дать мне это предупреждение для if (std::is_pod<T>::value)
и т.д., но как мне обойти это? Я не могу найти способ избежать этой проверки, и там нет static if
в С++ (пока).
Можно ли использовать принцип SFINAE, чтобы избежать этого if
?
Ответы
Ответ 1
Можно ли использовать принцип SFINAE, чтобы избежать этого, если?
Да, по крайней мере, для случаев, не связанных с дефолтом:
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));
}
Если вам нужна ошибка времени выполнения, а не времени компиляции, для неподдерживаемых типов, тогда объявите переменную функцию, чтобы поймать любые аргументы, которые не соответствуют другим перегрузкам.
CByteArray serialize(...)
{
hlassert_unconditional("Unsupported type");
return CByteArray();
}
Ответ 2
Вы можете использовать что-то вроде:
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>{});
}
Ответ 3
У меня возникло бы желание оставить его, как есть, честно. Компилятор демонстрирует, что знает, что неиспользуемые ветки можно оптимизировать. Конечно, предупреждение немного перетаскивается, но..
В любом случае, если вы действительно этого хотите, используйте std::enable_if
для возвращаемого типа функции.
Ответ 4
Как насчет этого? 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);
}
Эта документация довольно хороша для enable_if
: http://en.cppreference.com/w/cpp/types/enable_if
Ответ 5
Теперь вы можете использовать ограничения шаблонов, чтобы исправить это, мне нравится использовать небольшой макрос, чтобы сделать шаблончик enable_if
немного понятным:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
Затем вы можете определить их непосредственно в функции:
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));
}
Однако это становится очень уродливым очень быстро. Во-первых, вы должны отказаться от других условий, чтобы избежать двусмысленности. Во-вторых, функции должны быть упорядочены так, чтобы другие функции объявлялись или определялись до их рекурсивного вызова. Это не очень хорошо масштабируется. Если вам нужно добавить дополнительные условия в будущем, это может стать намного сложнее.
Лучшим решением является использование условной перегрузки с помощью комбинатор исправлений. Библиотека Fit предоставляет conditional и fix, поэтому вам не нужно писать самостоятельно. Итак, в С++ 14 вы можете написать:
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();
}
));
Однако, если вы еще не используете С++ 14, вам придется записывать их как объекты функций:
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 = {};
Наконец, для вашего случая, вы можете отказаться от другой части, если у вас действительно нет необходимости в проверке времени выполнения. Тогда вы можете просто иметь две перегрузки:
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));
}
));
Итак, вместо этого у вас будет ошибка компилятора. Самое приятное в использовании enable_if
и ограничений заключается в том, что ошибка будет в коде пользователя, а не в коде (с некоторой длинной обратной сетью). Это помогает понять, что пользователь делает ошибку, а не проблему с кодом библиотеки.