11

Perhaps something similar has already been asked, and sure, it's a nitpick...

I have a bunch of constant std::maps to switch between enum (class) values and their std::string representations (both ways). Someone here pointed out to me that these maps will be initialized at runtime, when other initialization code is run, before my program executes all the good stuff. This would mean constant expressions would impact runtime performance, as the maps are built up from their enum-string pairs.

As an illustrative example, here is an example of one of these maps:

enum class os
{
    Windows,
    Linux,
    MacOSX
};
const map<string, os> os_map =
     { {"windows", os::Windows},
       {"linux",   os::Linux},
       {"mac",     os::MacOSX} };
const map<os, string> os_map_inverse =
     { {os::Windows, "windows"},
       {os::Linux,   "linux"},
       {os::MacOSX,  "mac"} };

Would the C++11 constexpr have any influence on performance, or is my assumption of a runtime initialization penalty false? I would think a compiler can embed a constant STL container as pure data in the executable, but apparently that may not be as easy as I make it sound?

3
  • 1
    Why don't you try boost::bimap for the two-sided mapping between the enum and its string representation? Much less likely to make an error when adding new values. Commented Nov 6, 2011 at 12:45
  • 1
    Xeo: pull in Boost for a thing as simple as this? No thanks, I'm dependency-free, and would really like to keep it that way ;)... I might even replace the string->enum map with an unordered_map and the enum->string map with a vector (enum values aren't important, they just count up one by one) for performance if that would improve anything. boost::bimap would suck in comparison :) Commented Nov 6, 2011 at 13:14
  • 2
    @rubenvb : And yet Boost.MultiIndex could do exactly that, much more succinctly, with 0 overhead. Please don't view Boost as a 'dependency'. Commented Nov 9, 2011 at 0:15

4 Answers 4

20

It's not so much the performance of initialization that is a problem, but the order of initialization. If someone uses your map before main has started (for example at initialization for a namespace scope variable), then you are SOL, because you are not guaranteed that your map has been initialized before the user's initialization uses it.

However you can do this thing at compile time. String literals are constant expressions, as are enumerators. A simple linear-time complexity structure

struct entry {
  char const *name;
  os value;
};

constexpr entry map[] = {
  { "windows", os::Windows },
  { "linux", os::Linux },
  { "mac", os::Mac }
};

constexpr bool same(char const *x, char const *y) {
  return !*x && !*y ? true : (*x == *y && same(x+1, y+1));
}

constexpr os value(char const *name, entry const *entries) {
  return same(entries->name, name) ? entries->value : value(name, entries+1);
}

If you use value(a, b) in a constant expression context, and the name you specify doesn't exist, you will get a compile time error because the function call would become non-constant.

To use value(a, b) in a non-constant expression context, you better add safety features, like adding an end marker to your array and throwing an exception in value if you hit the end marker (the function call will still be a constant expression as long as you never hit the end marker).

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

8 Comments

Seems it's not working (GCC 4.5.1): ideone.com/w8QFN . Do you think it's a compiler issue?
@atzz yes it's a compiler issue. Try GCC4.6.
Johannes, thanks for the reply; I will, tomorrow. Don't have the compiler available at the moment.
With GCC4.6 it works as expected, and indeed gcc.gnu.org/projects/cxx0x.html shows that constexpr support was added in 4.6. Johannes, sorry for all this back-and-forth; I shouldn't have been dabbling in C++ at Sunday evening ;)
!*x && !*y return true for both nullptr's. Right? So why true? What should return nullptr==nullptr? As for me false. And there should also be if( x==y ) simple optimization.
|
4

constexpr does not work on arbitrary expressions, and especially not on things that will use the freestore. map/string will use the freestore, and thus constexpr will not work for initializing them at compile time, and have no code run at runtime.

As for the runtime penalty: Depending on the storage duration of those variables (and I assume static here, which means initialization before main), you will not even be able to measure the penalty, especially not in the code that is using them, assuming you will use them many times, where the lookup has much more "overhead" than the initialization.

But as for everything, remember rule one: Make things work. Profile. Make things fast. In this order.

Comments

3

Ah yes, it's a typical issue.

The only alternative I've found to avoid this runtime initialization is to use plain C structures. If you're willing to go the extra-mile and store the values in a plain C array, you can get static initialization (and reduced memory footprint).

It's one of the reason LLVM/Clang actually use the tblgen utility: plain tables are statically initialized, and you can generate them sorted.

Another solution would be to create a dedicated function instead: for the enum to string conversion it's easy to use a switch and let the compiler optimize it into a plain table, for the string to enum it's a bit more involved (you need if/else branches organized right to get the O(log N) behavior) but for small enums a linear search is as good anyway, in which case a single macro hackery (based off Boost Preprocessor goodness) can get you everything you need.

Comments

0

I have recently found my own best way to furnish an enum. See the code:

enum State {
    INIT, OK, ENoFou, EWroTy, EWroVa
};

struct StateToString {
    State state;
    const char *name;
    const char *description;
public:
    constexpr StateToString(State const& st)
            : state(st)
            , name("Unknown")
            , description("Unknown")
    {
        switch(st){
            case INIT  : name = "INIT"  , description="INIT";              break;
            case OK    : name = "OK"    , description="OK";                break;
            case ENoFou: name = "ENoFou", description="Error: Not found";  break;
            case EWroTy: name = "EWroTy", description="Error: Wrong type"; break;
            case EWroVa: name = "EWroVa", description="Error: Wrong value, setter rejected"; break;
            // Do not use default to receive a comile-time warning about not all values of enumeration are handled. Need `-Wswitch-enum` in GCC
        }
    }
};

Extending this example to some universal code, let's name it lib code:

/// Concept of Compile time meta information about (enum) value.
/// This is the best implementation because:
///   - compile-time resolvable using `constexpr name = CtMetaInfo<T>(value)`
///   - enum type can be implemented anywhere
///   - compile-time check for errors, no hidden run-time surprises allowed - guarantied by design. 
///   - with GCC's `-Wswitch` or `-Werror=switch` - you will never forget to handle every enumerated value
///   - nice and simple syntaxis `CtMetaInfo(enumValue).name`
///   - designed for enums suitable for everything
///   - no dependencies
/// Recommendations:
///   - write `using TypeToString = CtMetaInfo<Type>;` to simplify code reading
///   - always check constexpr functions assigning their return results to constexpr values
/**\code

    template< typename T >
    concept CtMetaInfo_CONCEPT {
        T value;
        const char *name;
        // Here could add variables for any other meta information. All vars must be filled either by default values or in ctor init list
    public:
        ///\param defaultName will be stored to `name` if constructor body will not reassign it
        constexpr CtMetaInfoBase( T const& val, const char* defaultName="Unknown" );
    };

   \endcode
 */

/// Pre-declare struct template. Specializations must be defined.
template< typename T >
struct CtMetaInfo;

/// Template specialization gives flexibility, i.e. to define such function templates
template <typename T>
constexpr const char* GetNameOfValue(T const& ty)
{ return CtMetaInfo<T>(ty).name; }

User must define specialization for user's type:

/// Specialization for `rapidjson::Type` 
template<>
struct CtMetaInfo<Type> {
    using T = Type;
    T value;
    const char *name;
public:
    constexpr CtMetaInfo(T const& val)
            : value(val)
            , name("Unknown")
    {
        switch(val){
            case kNullType                 : name = "Null"  ; break;
            case kFalseType: case kTrueType: name = "Bool"  ; break;
            case kObjectType               : name = "Object"; break;
            case kArrayType                : name = "Array" ; break;
            case kStringType               : name = "String"; break;
            case kNumberType               : name = "Number"; break;
        }
    }
    static constexpr const char* Name(T const& val){
        return CtMetaInfo<Type>(val).name;
    }
};

/// TEST
constexpr const char *test_constexprvalue = CtMetaInfo<Type>(kStringType).name;
constexpr const char *test_constexprvalu2 = GetNameOfValue(kNullType);

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.