简体   繁体   中英

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?

I do not know if you absolutely need the GetSize() function for some other purpose, but in your GetArray you already have access to the length of the array so you could just do this:

#include <array>

template <std::size_t N>
constexpr auto
GetArray(const char(&format)[N])
{
    return std::array<char, N*2>{};
}

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

The problem with constexpr functions is, that you can call the with both constexpr arguments as well as 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)>()

Here is a C++17 working solution using lambda to wrap the char array as a constexpr.

#include <array>
#include <string>

template <typename Char_Array_Holder>
constexpr size_t GetSize(Char_Array_Holder holder)
{
  // Iterate over format array and compute a value.
  constexpr std::string_view format = holder();

  size_t count = 0;
  for (char c : format)
  {
    if (c == '%')
    {
      ++count;
    }
  }

  return count;
}

template <typename Char_Array_Holder>
constexpr auto GetArray(Char_Array_Holder holder)
{
  constexpr size_t arraySize = GetSize(holder);
  return std::array<char, arraySize>();
}

#define hold(msg) []() { return msg; }

int main(int argc, char** argv)
{
  static_assert(GetArray(hold("hello")).size() == 0, "failed...");
  static_assert(GetArray(hold("h%ello")).size() == 1, "failed...");
  static_assert(GetArray(hold("h%el%%lo")).size() == 3, "failed...");
}

Adapted from compile-time-string-parsing you can read to get futher information

GCC is wrong in accepting this code here.

The format in GetArray body is not a constexpr (it's a reference to a array of size const char), and as such, it can't be used in another constexpr function as a constexpr . Please notice that size is a constexpr so you should use that instead:

#include <array>


inline constexpr size_t GetSize(size_t size) { return size * 2; }

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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