Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize array of compile time defined size as constant expression

I have an array of strings that must be allocated once and their underlying c_str must remain valid for entire duration of the program.

There's some API that provides info about some arbitrary data types. Could look like this:

// Defined outside my code
#define NUMBER_OF_TYPES 23
const char* getTypeSuffix(int index);

The getTypeSuffix is not constexpr, so this must work at least partially in runtime.

The interface that I must provide:

// Returned pointer must statically allocated (not on stack, not malloc)
const char* getReadableTypeName(int type);

Now my array should have following type:

std::string typeNames[NUMBER_OF_TYPES];

For my purposes, it will be initialized within a wrapper class, right in the constructor:

class MyNames
{
  MyNames()
  {
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
      names[i] = std::string("Type ") + getTypeSuffix(i);
    }
  }

  const char* operator[](int type) { return _names[(int)type].c_str(); }

private:
  std::string _names[NUMBER_OF_TYPES];
};

This is then used in an singleton-ish kind of way, for example:

const char* getReadableTypeName(int type) 
{
  static MyNames names;
  return names[type];
}

Now what I want to improve is that I can see that the for loop in the constructor could be replaced as such:

 MyNames() : _names{std::string("Type ") + getTypeSuffix(0), std::string("Type ") + getTypeSuffix(1), ... , std::string("Type ") + getTypeSuffix(NUMBER_OF_TYPES-1)}
 {}

Obviously a pseudocode, but you get the point - the array can be initialized directly, leaving the constructor without body, which is neat. It also means that the array member _names can be const, further enforcing the correct usage of this helper class.

I'm quite sure there would be many other uses to filling an array by expressions in compile time, instead of having loop. I would even suspect that this is something that happens anyway during 03.

Is there a way to write a C++11 style array initializer list that has flexible length and is defined by an expression? Another simple example would be:

constexpr int numberCount = 10;
std::string numbers[] = {std::to_string(1), std::to_string(2), ... , std::to_string(numberCount)};

Again, an expression instead of a loop.

I'm not asking this question because I was trying to drastically improve performance, but because I want to learn about new, neat, features of C++14 and later.

like image 594
Tomáš Zato - Reinstate Monica Avatar asked Apr 16 '26 03:04

Tomáš Zato - Reinstate Monica


1 Answers

instead of C-array use std::array, then you might write your function to return that std::array and your member can then be const:

std::array<std::string, NUMBER_OF_TYPES> build_names()
{
    std::array<std::string, NUMBER_OF_TYPES> names;
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
          names[i] = std::string("Type ") + getTypeSuffix(i);
    }
    return names;
}


class MyNames
{
  MyNames() : _names(build_names()) {}
  const char* operator[](int type) const { return _names[(int)type].c_str(); }

private:
  const std::array<std::string, NUMBER_OF_TYPES> _names;
};

Now you have std::array, you might use variadic template instead of loop, something like (std::index_sequence stuff is C++14, but can be implemented in C++11):

template <std::size_t ... Is> 
std::array<std::string, sizeof...(Is)> build_names(std::index_sequence<Is...>)
{
     return {{ std::string("Type ") + getTypeSuffix(i) }};
}

and then call it:

MyNames() : _names(build_names(std::make_index_sequence<NUMBER_OF_TYPES>())) {}
like image 171
Jarod42 Avatar answered Apr 18 '26 16:04

Jarod42