M.Mac
M.Mac

Reputation: 739

Convert enum array to parameter pack

I am struggling with the creation of an object, because i have to create a new parameter pack from an array inside a function. The array contains a few enum elements of the same enum type.

The error message states, that the array is not static, thus not a valid input that could be converted into a parameter pack.

Is there a possibility to something as follows:

enum Enums{e1, e2};

template<typename T, auto ...Enums>
class Example{};

template<int size>
constexpr auto createObj(){

    Enums enum_array[size] = {};

    for(int i = 0; i< size ; i++){
        //fill array with e1 and e2
    }

    return Example<double, enum_array>();
}

Error message:

error: the address of ‘enum_array’ is not a valid template argument because it does not have static storage duration
return Example<double, enum_array>();

Upvotes: 2

Views: 701

Answers (1)

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

You'll probably need a few indirection level. First, to use any value as template parameter, it must be a compile time constant for the compiler to instanciate the template and generate its code.

For that, you'll need to put the function calculating the array in a constexpr function:

template<int size>
constexpr auto createObjArray() {
    auto enum_array = std::array<Enums, size>{};

    for(int i = 0; i < size ; i++){
        //fill array with e1 and e2
    }

    return enum_array;
}

Then, you'll need a way to expand the array into a pack. Right now in C++17 (and C++20) it cannot be done locally. You'll need an index sequence of some sort. The most straightforward way to do that is to use an helper function:

template<std::size_t... S>
constexpr auto createObjHelper(std::index_sequence<S...>){
    constexpr auto enumArray = createObjArray<sizeof...(S)>();

    return Example<double, enumArray[S]...>();
}

Then wrap all that from a single user facing function:

template<int size>
constexpr auto createObj(){
    return createObjHelper(std::make_index_sequence<size>());
}

Note that many of those helper function can be achieved using lambdas. For example, a lambda can have a constexpr body and be executed at compile time inside a runtime function:

template<std::size_t... S>
constexpr auto createObjHelper(std::index_sequence<S...>){
    //   implicitly constexpr ---v
    constexpr auto enumArray = []{
        auto enum_array = std::array<Enums, size>{};

        for(int i = 0; i < size ; i++){
            //fill array with e1 and e2
        }

        return enum_array;
    }();

    return Example<double, enumArray[S]...>();
}

And In C++20, you can use familiar template function syntax for lambda to avoid the helper function:

template<int size>
constexpr auto createObj(){
    //   implicitly constexpr ---v
    constexpr auto enumArray = []{
        Enums enum_array[size] = {};

        for(int i = 0; i < size ; i++){
            //fill array with e1 and e2
        }

        return enum_array;
    }();

    return []<std::size_t... S>(std::index_sequence<S...>) -> decltype(auto) {
        return Example<double, enumArray[S]...>();
    }(std::make_index_sequence<size>());
}

In C++23, if structured binding pack introduction is approved, then the second lambda can be removed in favor of creating a new pack directly in the function:

template<int size>
constexpr auto createObj(){
    //   implicitly constexpr ---v
    constexpr auto enumArray = []{
        Enums enum_array[size] = {};

        for(int i = 0; i < size ; i++){
            //fill array with e1 and e2
        }

        return enum_array;
    }();

    constexpr auto&& [...allEnums] = enumArray;

    return Example<double, allEnums...>();
}

Upvotes: 1

Related Questions