Otto Lewis
Otto Lewis

Reputation: 27

Check at compile time if map of functions has duplicates and same size of struct field count

I have a struct with 3 fields, I want to create a map of lambda/function pointers with a key of a hash of the string value of the struct field name. At compile time the map is the checked with an assert: correct length - number of fields in the struct and has no duplicate keys. This works with a map of long as the key and value but not when the value is a lambda. Is there a way to make this work with C++17?

When this is attempted I get error:

error: the type 'const std::pair<long long int, std::function<void(int)> > []' of 'constexpr' variable 'ini' is not literal 107 | constexpr std::pair<long long, std::function<void(int)>> ini[]{

See example here: https://godbolt.org/z/3MjTq94rj Uncomment MakeMapFunction

Header.hpp

struct Container {
    int fieldA;
    std::string fieldB;
    bool fieldC;
};

constexpr inline long long int hash(char const *str, int h = 0)
{
    return (!str[h] ? 5381 : (hash(str, h + 1) * 7) ^ str[h]);
}

template <typename T, typename V>
constexpr bool hasDuplicates(const std::pair<T, V> *array, std::size_t size)
{
    for (std::size_t i = 1; i < size; i++)
    {
        for (std::size_t j = 0; j < i; j++)
        {
            if (array[i].first == array[j].first)
            {
                return 1;
            }
        }
    }
    return 0;
}
// Start of check number of fields in struct
template <typename T, typename... TArgs>
auto isAggregateConstructableImpl(std::tuple<TArgs...>) -> decltype(T{std::declval<TArgs>()...});

template <typename T, typename TArgs, typename = void>
struct isAggregateConstructable : std::false_type
{
};

template <typename T, typename TArgs>
struct isAggregateConstructable<T, TArgs, std::void_t<decltype(isAggregateConstructableImpl<T>(std::declval<TArgs>()))>> : std::true_type
{
};

// Checks if type can be initialized from braced-init-list.
template <typename T, typename TArgs>
constexpr auto isAggregateConstructableV = isAggregateConstructable<T, TArgs>::value;

// Class is convertible to anything.
class any
{
public:
    template <typename T>
    operator T() const;
};

template <class T, typename... TArgs>
constexpr std::size_t numBindingsImpl()
{
    if constexpr (isAggregateConstructable<T, std::tuple<TArgs...>>())
    {
        return numBindingsImpl<T, any, TArgs...>();
    }
    else
    {
        return sizeof...(TArgs) - 1;
    }
};

template <typename T>
constexpr auto structGetNumberOfFields = numBindingsImpl<T, any>();

class Holder
{
public:
    static std::map<long long, long> MakeMapString();
    static const std::map<long long, long> list;
};

source.cpp

std::map<long long, long> Holder::MakeMapString()
{
    constexpr std::pair<long long, long> ini[]{
        {hash("fieldA"), 55}, // if removed then first static_assert will fail
        {hash("fieldB"), 77}, // if changed to fieldA then second static_assert will fail
        {hash("fieldC"), 99}};

    // This assert determines if the length of ini is the same as number of fields in Container struct
    static_assert(end(ini) - begin(ini) == structGetNumberOfFields<Container>, "[Container] has incorrect number of fields");
    // This assert checks there are no duplicates in ini
    static_assert(!hasDuplicates(ini, std::extent_v<std::remove_reference_t<decltype(ini)>>), "Duplicates Found!");

    return {std::begin(ini), std::end(ini)};
}

const std::map<long long, long> listOf = Holder::MakeMapString();

Upvotes: 1

Views: 91

Answers (1)

Jan Schultke
Jan Schultke

Reputation: 39668

You cannot have a constexpr std::function object. Keep in mind that std::function performs type erasure and dynamic allocations, and non-transient allocations aren't supported yet. In other words, any memory that std::function allocates would have to be freed before the end of the constant expression.

In your example, none of the lambdas you're making have captures, so you could also turn them into function pointers:

// MakeMapFunction is pointless.
// Let's just use an immediately invoked lambda expression (IILE)

const std::map<long long, void(*)(int)> listOfFunction = [] {
    static constexpr std::pair<long long, void(*)(int)> ini[] {
        {hash("fieldA"), [](int a) {}},
        {hash("fieldB"), [](int a) {}},
        {hash("fieldC"), [](int a) {}}
    };

    // static assertions here ...

    return {std::begin(ini), std::end(ini)};
}();

Or, more concisely:

// note: a std::map with hash keys makes very little sense, and is
//       basically imitating a std::unordered_map
const std::unordered_map<std::string_view, void(*)(int)> listOfFunction = {
    { "fieldA", [](int a) {} },
    { "fieldB", [](int a) {} },
    { "fieldC", [](int a) {} }
};

// static assertions here ...

Upvotes: 1

Related Questions