1

I have a small table of structs, that I would like to refer in code by a short name. However, using a string as name would mean to have some lookup search every time this table needs to be accessed. So I wonder if it would be possible to define enum values in the array initializer to name them? Something like this:

struct Parameter {
    EnumType name; // TODO how to declare this?
    int min;
    int max;
};

// now define the array and EnumType values at the same time:
Parameter parameters[]={
    {FIRST_PARAMETER, 0, 100},
    {SECOND_PARAMETER, 10, 20}
}

// now access like that:
int min = parameters[FIRST_PARAMETER].min;

So basically, enum values FIRST_PARAMETER = 0 and SECOND_PARAMETER = 1 would be created while initializing the array, and I can use this names instead of 0 and 1 later to access the arrays elements.

Is there any standard way in C++ to do this? Are macros or templates needed and able do this?

6
  • it's easy to write a script to stamp out an enum with the parameters you want. enum values default to zero for the first and increase by one for each next enumerator so it's trivial to stamp that out. Then you just use those enumerators like you have in your code. Do note that you don't really need to enum member in the class object if you are only using it for the array access. Commented Oct 16, 2024 at 20:33
  • 2
    Real-world use of X-Macros Commented Oct 16, 2024 at 20:35
  • ^ Nice.. I will try that out. Commented Oct 16, 2024 at 20:49
  • I think a bit more context is needed to understand what you are trying to do to see if there is a better way to do it, e.g. might the parameters be supplied out of order? However to answer your direct question you can nest the declaration of the enums within the struct Parameters, something like: enum EnumType { FIRST = 0, SECOND = 1, };. Then you can reference that with Parameter::FIRST and Parameter::SECOND. parameters should be declared constexpr. Commented Oct 16, 2024 at 20:55
  • I do not understand what is the point. Consider using 0 and 1 instead of FIRST and SECOND and just use std::map<int, ...> Commented Oct 16, 2024 at 21:38

2 Answers 2

1

Even if it doesn't directly answer the question, you may have an alternative by using a compile-time map as shown here. There is no lookup overhead since everything is done at compile time.

By using std::string_view as key, you can achieve the same purpose you are looking for. Note that Parameter doesn't need anymore to hold the member name since it corresponds to the key of the map.

#include <string_view>

// https://stackoverflow.com/questions/61281843/creating-compile-time-key-value-map-in-c

template <class Key, class Value, int N>
class CTMap 
{
public:
    struct KV { Key key;  Value value; };

    constexpr Value  operator[](Key key) const  {  return Get(key); }

private:
    constexpr Value  Get(Key key, int i = 0) const
    {
        return i == N ?
               KeyNotFound() :
               pairs[i].key == key ? pairs[i].value : Get(key, i + 1);
    }

    static Value KeyNotFound() // not constexpr
    {
        return {};
    }

public:
    KV  pairs[N];
};

////////////////////////////////////////////////////////////////////////////////
struct Parameter {
    int min;
    int max;
};

constexpr CTMap<std::string_view, Parameter, 2> parameters
{{ 
    {"A",{1,2} },  
    {"B",{3,4} }
}};

static_assert (parameters["A"].min == 1);
static_assert (parameters["A"].max == 2);
static_assert (parameters["B"].min == 3);
static_assert (parameters["B"].max == 4);

int main()
{
}

Demo

It should work starting with c++17


Update: Following @InTheWonderlandZoo's comment, it may be possible to overcome the issue when the key doesn't exist.

You could for instance provide only a find method that both returns a boolean telling whether the key has been found and the value if found (or default constructed value).

The other option is to provide a at method that can throw an exception if the key is not found.

This post shows for instance how to use the fact that a constepxr may be used at compile or runtime. So if we throw an exception when the key doesn't exist, we can get a compilation error

#include <string_view>
#include <iostream>
#include <exception>

////////////////////////////////////////////////////////////////////////////////
template <class Key, class Value, int N>
struct CTMap 
{
    struct KV { Key key;  Value value; };

    constexpr auto  find (Key key) const
    {
        for (int i=0; i<N; i++)
        {
            if (pairs[i].key == key)  { return std::make_pair(true,pairs[i].value); }
        }
        return std::make_pair (false, Value{});
    }
    
    constexpr Value  at (Key key) const
    {  
        auto lookup = find (key);
        if (not lookup.first)  {  throw std::out_of_range ("key not found"); } 
        return lookup.second;
    }

    KV  pairs[N];
};

////////////////////////////////////////////////////////////////////////////////
struct Parameter {
    int min;
    int max;
};

constexpr CTMap<std::string_view, Parameter, 2> parameters
{{ 
    {"A",{1,2} },  
    {"B",{3,4} }
}};

// The compiler will ignore the runtime part. 
static_assert (parameters.at("A").min == 1);
static_assert (parameters.at("A").max == 2);
static_assert (parameters.at("B").min == 3);
static_assert (parameters.at("B").max == 4);

// The compiler will try to use the runtime part 
// which will imply a compilation error.  
// static_assert (parameters.at("C").max == 0);

static_assert (parameters.find("B").first == true);
static_assert (parameters.find("C").first == false);

int main()
{
    constexpr auto pa = parameters.at("A");
    
    // Same thing here: should have a compilation error
    // constexpr auto pc = parameters.at("C");
     
    try 
    {
        std::cout << parameters.at("C").max << "\n";   // OUPS...
    }
    catch (std::out_of_range const& exc)
    {
        std::cout << exc.what() << '\n';
    }
}

For instance, constexpr auto pa = parameters.at("A"); is valid but one will get a compilation error with constexpr auto pc = parameters.at("C");:

foo.cpp: In function 'int main()':
foo.cpp:58:38:   in 'constexpr' expansion of 'parameters.CTMap<std::basic_string_view<char>, Parameter, 2>::at(std::basic_string_view<char>(((const char*)"C")))'
foo.cpp:23:35: error: expression '<throw-expression>' is not a constant expression
   23 |         if (not lookup.first)  {  throw std::out_of_range ("key not found"); }
      |                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

And if you try to use auto pc = parameters.at("C");, you won't have a compilation error but an exception will be thrown at runtime.

Demo

Sign up to request clarification or add additional context in comments.

3 Comments

I like this very much, but I would add a caution that you need to be careful to ensure that you lookup the parameters with constexpr expressions, e.g. constexpr auto amin = parameters["A"].min;. The danger if you don't specify the constexpr, you might silently fail to lookup a missing key, and it will just give you the value 0 auto missingmin = parameters["MISSING"].min; i.e. it returns a default initialized value when the key is missing.
@InTheWonderlandZoo, yes indeed, one way to avoid this would be to provide only a method lookup returning a std::pair with 1) a boolean telling whether the key exists and 2) the value if the key exists otherwise a default constructed object (assuming the class is default constructible). The drawback would be a less verbose usage for the end user.
edrezen, i haven't thought too deeply about it, but instinctively I would think about returning std::optional instead of a std::pair, as I would find that to be more idiomatic.
0

depending on your application it can be rewritten like this

struct Parameter {
    int min;
    int max;
};

std::map <EnumType, Parameter> parametersMap {
    { FIRST_PARAMETER, { 0, 100 }},
    { SECOND_PARAMETER, { 0, 100 }},
};

int min = parametersMap[FIRST_PARAMETER].min;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.