如何避免"条件表达式是不变的"在模板代码中使用编译时常量条件进行警告?

时间:2014-10-31 13:57:57

标签: c++ templates

考虑代码:

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)等给我这个警告是正确的,但我该怎样绕过这个呢?我找不到避免这种检查的方法,而且C ++中还没有static if

可以使用SFINAE原则来避免这种情况if吗?

5 个答案:

答案 0 :(得分:10)

  

如果可以使用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();
}

答案 1 :(得分:6)

您可以使用以下内容:

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 :(得分:5)

坦率地说,我很想放弃它。编译器证明它知道可以优化掉未使用的分支。当然,警告有点拖累,但是......

无论如何,如果你真的想这样做,请在函数的返回类型上使用std::enable_if

答案 3 :(得分:5)

这是怎么回事? 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_ifhttp://en.cppreference.com/w/cpp/types/enable_if

答案 4 :(得分:2)

现在,您可以使用模板约束来修复它,我喜欢使用一个小宏来帮助使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));
}

然而,这很快变得丑陋。首先,你必须否定其他条件以避免歧义。其次,必须对函数进行排序,以便在递归调用其他函数之前声明或定义它们。它并没有很好地扩展。如果您将来需要添加其他条件,可能会变得更加复杂。

更好的解决方案是将conditional overloadingfix point combinator一起使用。 Fit库提供了conditionalfix适配器,因此您无需编写自己的适配器。所以在C ++ 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();
    }
));

但是,如果您还没有使用C ++ 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 = {};

最后,对于您的具体情况,您可以删除else部分,除非您确实需要运行时检查。然后你就可以有两个重载:

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和约束的好处是错误将出现在用户代码中而不是代码中(带有一些长回溯)。这有助于明确用户是出错的人而不是库代码的问题。