簡體   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