Как избежать "условного выражения является постоянным" предупреждением с условием постоянной компиляции в коде шаблона?

Рассмотрим код:

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 и ограничений заключается в том, что ошибка будет в коде пользователя, а не в коде (с некоторой длинной обратной сетью). Это помогает понять, что пользователь делает ошибку, а не проблему с кодом библиотеки.