lundi 23 septembre 2019

why clang doesn't compile this if constexpr

I made some template functions to ease the process of extracting data from json using rapidjson . I'm using visual studio 2017 so I have access to c++ 17 features and also I installed clang 8.0.1 and added it to visual studio because clang errors and warnings are better than msvc .

now this is the problem :

template <std::size_t N>
using JsonMembers = std::array<const char*, N>;

template <class T, std::size_t N>
using JsonVars = std::array<T *, N>;

namespace JsonUtils
{
    template <std::size_t N>
    bool VerifyMembers(const rapidjson::Value& JsonValue, const std::array<const char*, N> &members)
    {
        for (auto member : members)
            if (!JsonValue.HasMember(member))
                return false;
        return true;
    }

    template <class T, bool Optional, std::size_t N>
    bool DumpVarsOrOpts(const rapidjson::Value& JsonValue, const JsonMembers<N> &members, const JsonVars<T, N> &vars)
    {
        if constexpr (!Optional)
        {
            if (!VerifyMembers(JsonValue, members))
                return false;
        }

        auto members_begin = std::begin(members);
        auto members_end = std::end(members);

        auto vars_begin = std::begin(vars);
        auto vars_end = std::end(vars);

        for (; members_begin != members_end && vars_begin != vars_end; ++members_begin, ++vars_begin)
        {
            if (!JsonValue.HasMember(*members_begin))
            {
                continue;
            }

            if constexpr (std::is_same_v<T, bool>)
            {
                **vars_begin = JsonValue[*members_begin].GetBool();
            }

            else if constexpr (std::is_integral_v<T>)
            {
                if constexpr (std::is_unsigned_v<T>)
                {
                    if constexpr (sizeof(T) != sizeof(int64_t))
                        **vars_begin = JsonValue[*members_begin].GetUint();
                    else
                        **vars_begin = JsonValue[*members_begin].GetUint64();
                }
                else
                {
                    if constexpr (sizeof(T) != sizeof(int64_t))
                        **vars_begin = JsonValue[*members_begin].GetInt();
                    else
                        **vars_begin = JsonValue[*members_begin].GetInt64();
                }
            }

            else if constexpr (std::is_floating_point_v<T>)
            {
                **vars_begin = JsonValue[*members_begin].GetDouble();
            }

            else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, rad::string> ||
                std::is_same_v<T, std::optional<std::string>> || std::is_same_v<T, std::optional<rad::string>>)
            {
                **vars_begin = JsonValue[*members_begin].GetString();
            }

            else // here the problem
            {
                static_assert(false, "unsupported type !"); // this is hit by clang but not msvc
            }

        }

        return true;
    }

    template <class T, std::size_t N>
    bool DumpVars(const rapidjson::Value& JsonValue, const JsonMembers<N> &members, const JsonVars<T, N> &vars)
    {

return DumpVarsOrOpts(JsonValue, members, vars); }

template <class T, std::size_t N>
void DumpOpts(const rapidjson::Value& JsonValue, const JsonMembers<N> &members, const JsonVars<T, N> &vars)
{
    DumpVarsOrOpts<T, true, N>(JsonValue, members, vars);
}

}

the class rad::string is derived from std::string and doesn't have any additional members . I made it to add some operations to std::string such as split, replace_all, easy conversion from utf-8 to utf-16 and vice versa . so it basically looks like this :

namespace rad
{
    template<
        class CharT,
        class Traits = std::char_traits<CharT>,
        class Allocator = std::allocator<CharT>
    >
    class basic_string : public std::basic_string<CharT, Traits, Allocator>
    {
        ... some methods
    }

    using string = basic_string<char>;
    using wstring = basic_string<wchar_t>;
    using u8string = string;
    using u16string = basic_string<char16_t>;
    using u32string = basic_string<char32_t>;

}

now I used the functions like this :

bool some_class::some_method(const rapidjson::Value & Jv)
{
   if (!JsonUtils::DumpVars<std::string>(Jv, JsonMembers<4>{ "m1", "m2", "m3", "m4" },
        { &v1, &v2, &v3, &v4})) // the are all std::string
        return false;

   JsonUtils::DumpOpts<std::optional<std::string>>(Jv, JsonMembers<2>{ "mopt1", "mopt2" }, { &varopt1, &varopt2 }); // the are all std::optional<std::string>
   return true;
}

this compiles fine with msvc with c++ 17 enabled although the ide shows some errors around the last else branch

when I tried to compile it using clang I got an error :

error : static_assert failed "unsupported type !"
note: in instantiation of function template specialization 'JsonUtils::DumpVars<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, 4>' requested here

it seems that clang doesn't handle this if constexpr well . I thought I needed to enable c++ 17 features for clang but I remembered that the rest of the code contains many c++ 17 code and if it lacked c++ 17 support it would have complained about the if constexpr expression at the first place .

can any one here help me find the problem !

Aucun commentaire:

Enregistrer un commentaire