Compile time functions

While C++ lags behind new languages for support at compile time, there is some ability to get some of the advantages.

A simple case is to create a allow a runtime function that is simple enough to be run at compile time. This has the nice feature that if the function is not able to be used at compile time it will still work at runtime. Thus this can be done for many functions:

static constexpr u32 function(const char* const int)

However this flexibility also has the problem that you cant be sure it will be run at run time.

What is needed is a way to get the compile to tell you that is wont be able to do it. Check out this solution to this problem: consteval in C++17

It goes further but it explains a simple approach that is a nice feature to make the compiler warn if it cant make it a constexpr. Just assign the result of the constexpr function to a constexpr value. The compiler will then warn if this is not possible.

The following is a simple example:

// ELF hash is an even cheaper hash to run on strings.
// https://en.wikipedia.org/wiki/PJW_hash_function
static constexpr u32 elfHash(const char* const file)
{
    u32         hash{0};
    u32         high{0};
    for (const char* character = file; *character != '\0'; ++character)
    {
        hash <<= 4;
        hash += (*character);
        if (high = hash & 0xF000'0000) hash ^= high >> 24;
        hash &= ~high;
    }
    return hash;
}

...
    constexpr const u32 hash = elfHash(__FILE__)

It can also be in an if constexpr block and that will also do this check. This can be seen in the next compile time trick below.

Runtime checking of a public member function or member

It is possible to detect if a class has a function or member at run time and then have different code paths based on that. For example if a hash map uses a key that has its own hash function supplied it can be used directly as part of the hash map code, without any extra interface.

// Simpler declval() - can't deal with some cases like void etc.
template <typename type_t> type_t declaredFunction();

// typename needs to match but can be anything. SFINAE will match on the inner
// pert of the type expression. Using u32 as that is the return type of the
// hash function. If the hash function had a parameter, it too needs to be
// added. Like this:  
//      declaredFunction<key_t&>().hash(int())  
// To check a *public* member the obvious can be used:  
//      declaredFunction<key_t&>().size  
// This trick can't be done with functions because:  
//      They attempt to deduce the type of specialization by argument types.  
//      They can’t be partially specialized while classes can.  
//      They can’t have default template parameters while classes can. 
template <typename key_t, typename = u32>
struct hasHashStruct
{
    static constexpr bool value{false};
};

template <typename key_t>
struct hasHashStruct<key_t, decltype(u32(declaredFunction<key_t&>().hash()))>
{
    static constexpr bool value{true};
};

template <typename key_t> 
inline constexpr bool hasHash = hasHashStruct<key_t>::value;

...
template <typename key_t, typename, value_t>
class hashMap
{

    u32 hash;
    if constexpr (hasHash<key_t>)
    {
        hash = key_t.hash();
    }
    else
    {
        hash = _hash(&key)
    }

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.