Home > Mobile >  constexpr get index of item in array
constexpr get index of item in array

Time:01-21

I have the following code:

static const char* ITEMS[] = { "a", "b" }  // too many elements for a std::array in my situation

void my_func() {
    printf("a: %d", index_of(ITEMS, "a"));  // 0
    printf("d: %d", index_of(ITEMS, "d"));  // error at compile time; "d" does not exist in ITEMS.
}

How would I define a similar index_of method? I've seen various suggestions on other SO posts, but they don't work for the following reasons:

  • Use std::string/Make ITEMS a constexpr
    • Unfortunately ImGui's Combo requires a const char** for the array type which is impossible with std::string, and similarly, as ITEMS would be a constexpr it would be of type const char* const *, which does not work.
  • Use std::array instead of T[]
    • The number of elements is too high for the compiler to handle, resulting in a compiler error.

CodePudding user response:

First, you must declare the array as constexpr. Otherwise its values are not usable at compile-time:

static constexpr const char* ITEMS[] = { "a", "b" };

As a consequence the type of the elements will be const char* const. If a library function expects them to be non-const then presumably that is because the library may attempt to modify them. That is of course impossible if they are supposed to be compile-time constants.

If you are absolutely sure that the library is simply lying about needing to modify the pointer value, then you can cast const away with const_cast. However, if the library then does attempt to modify the elements, then your program will have undefined behavior.

Then you also need C 20. Otherwise there is no way to force a compile-time error from failure of constant expression evaluation in a simple function call. Specifically you need the consteval feature.

With that:

template<typename R>
consteval auto index_of(const R& range, std::string_view needle) {
    auto it = std::ranges::find(range, needle);
    if(it == std::ranges::end(range))
        throw std::logic_error("Element not found!");
    return std::ranges::distance(std::ranges::begin(range), it);
}

This works with both built-in arrays and std::array.

Then you also need to replace %d with %zu, because the iterator difference type returned by this index_of is std::size_t for built-in arrays. %d is for the wrong type (int).

  •  Tags:  
  • c
  • Related