繁体   English   中英

如何在模板代码中使用编译时常量条件避免“条件表达式是常量”警告?

[英]How to avoid “conditional expression is constant” warning with compile-time-constant conditions in template code?

考虑一下代码:

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在C ++中还没有static if

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

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

您可以使用以下内容:

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>{});
}

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

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

怎么样? 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

现在,您可以使用模板约束来修复它,我喜欢使用一个小宏来帮助使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库提供了条件修复适配器,因此您不必编写自己的适配器。 所以在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和约束的enable_if是错误将出现在用户代码中而不是代码中(有一些长回溯)。 这有助于清楚地表明用户是出错的人而不是库代码的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM