c++visual-studio-2019

Using constexpr as std::array size


I have the following code that uses a constexpr as the array size.

#include <array>

template <size_t size>
constexpr size_t
GetSize(const char(&format)[size])
{
    // Iterate over format array and compute a value.
    size_t count = 0;
    for (size_t i = 0; i < size; ++i)
    {
        if (format[i] == '%')
        {
            ++count;
        }
    }

    return count;
}

template <size_t size>
constexpr auto
GetArray(const char(&format)[size])
{
    constexpr size_t arraySize = GetSize(format);
    return std::array<char, arraySize>();
}

int main(int argc, char** argv)
{
    static_assert(GetArray("hello").size() == 12, "failed");
}

This however fails to compile under VS2019 with the following error

error C2131:  expression did not evaluate to a constant
message :  failure was caused by a read of a variable outside its lifetime
message :  see usage of 'format'
message :  see reference to function template instantiation 'auto GetArray<6>(const char (&)[6])' being compiled

Is this a compiler bug? If so, is there a workaround for this?


Solution

  • The problem with constexpr functions is that you can call the function with constexpr arguments or with non-constexpr arguments:

    int constexpr f(int n)
    {
        return ++n;
    }
    
    int constexpr n0 = 7;
    int n1; std::cin >> n1;
    f(n0); // result IS constexpr
    f(n1); // result is NOT constexpr, just an ordinary int
    

    Because of this characteristic the function parameters themselves cannot be constexpr, or more precisely, cannot be used in constexpr contexts. So in your function as well:

    constexpr size_t arraySize = getSize(format);
    //                                      ^ cannot be used as constexpr, even if
    //                                        constexpr has been passed to, so result
    //                                        not either (the f(n1) case above)
    

    You could modify your second function a bit:

    template <size_t Size>
    constexpr auto
    getArray()
    {
        return std::array<char, Size>();
    }
    

    And use it like

    int main(int argc, char** argv)
    {
        static_assert(getArray<getSize("hello")>().size() == 0, "failed");
        return 0;
    }
    

    Sure, looks rather ugly now, you might hide behind a macro, though:

    #define myGetArray(STRING) getArray<getSize(STRING)>()
    

    or simply

    #define getArray(STRING) std::array<char, getSize(STRING)>()