apopa
apopa

Reputation: 445

Can we create an std::array of pointers to object in a constexpr function and return it?

Suppose we have some sort of templated struct:

template<typename T>
struct S {};

We want to create an std::array of some object of type S. But, because S is a template struct, we need to create a base class for keeping those objects.

struct AbstractS {};

template<typename T>
struct S : AbstractS {};

Now, suppose that we have a constexpr function that creates an std::array and returns it and a constexpr function for get that array.

constexpr auto createArray() {
    constexpr auto v1 = S<int>{};
    constexpr auto v2 = S<double>{};
    std::array<const AbstractS *, 2> values{ &v1, &v2 };
    return values;
}

constexpr void getArray() {
    constexpr auto values = createArray();
}

This code doesn't compile and I think this is because the addresses of v1 and v2 are not constants.

Let me give you a concrete example of what I'm trying to do.

struct AbstractPolynomial {};

template<typename T, std::size_t Degree>
struct Polynomial : AbstractPolynomial {};

I have a struct that models a polynomial function, where T is type of values of coefficients of polynomial and Degree is the polynomial Degree.

template<std::size_t N>
constexpr auto createArray() {
    std::array<AbstractPolynomial *, N> polynomials;
    for (std::size_t i = 0; i < N; i++) {
        if (i % 2 == 0) {
            polynomials[i] = &Polynomials<T1>{5};
        } else {
            polynomials[i] = &Polynomials<T2>{2, 5};
        }
    }
    return polynomials;
}

Suppose that Polynomial has a deduction guide (I didn't implement it here). I know that you cannot get the address of a temporary and assigning lines are incorrect, but I put in this form because I want you to give me a solution for this scenario.

Can we do some sort of tricks to create a scenario like this?

Upvotes: 1

Views: 416

Answers (2)

alfC
alfC

Reputation: 16300

This is the solution, note that the Polynomials need to be generated initially preserving their compile-time type. The later choice to not use that information for whatever reason (dynamic access) is yours.

Note the data structure is constexpr, but not necessarily the evaluation of the polynomial. I think this is because virtual interferes with the constexpr-ness capabilities of the evaluate function.

You can play with the solution here: https://godbolt.org/z/oMreTzoGP

#include<array>
#include<cassert>
#include<tuple>
#include<utility>

// Dynamic polymials (questionable use of virtual functions)
struct AbstractPolynomial {
    virtual auto evaluate() const -> double = 0;
};

template<typename T>
struct Polynomial : AbstractPolynomial {
    constexpr Polynomial(T t) : value_{t}{}
    T value_;
    auto evaluate() const -> double override;
};

// instantiate and define two child classes for illustration
template<> auto Polynomial<double                   >::evaluate() const -> double {return value_;}
template<> auto Polynomial<std::pair<double, double>>::evaluate() const -> double {return value_.first + value_.second;}

// Metaprogramming in this block doesn't assume virtual functions, Polynomial can be a concrete class

// functional form (on index and constructor args) taken from OP example
constexpr auto makePoly(std::integral_constant<int, 0>){return Polynomial<double                   >{5.};}
constexpr auto makePoly(std::integral_constant<int, 1>){return Polynomial<std::pair<double, double>>({2., 5.});}

// Tuples (not arrays) are created here
template <std::size_t... I>
constexpr auto createTuple_aux(std::index_sequence<I...>){
    // do different things for even/odd cases (again taken from OP example)
    return std::make_tuple(makePoly(std::integral_constant<int, I % 2>{})...);
}
 
template <std::size_t N> constexpr auto createTuple(){return createTuple_aux(std::make_index_sequence<N>{});}

// create 10 non-polymorphic polynamials in a tuple (preserve type information)
constexpr auto polyTuple = createTuple<10>();

// create 10 polymorphic polynamials in an array via pointers (type information is kept in the virtual table in pointer elements)
constexpr auto polyArrayPtr = std::apply([](auto const&... e){return std::array<AbstractPolynomial const*, std::tuple_size<decltype(polyTuple)>{}>{&e...};}, polyTuple);

int main(){

// test non-polymorphic access
    assert( std::get<0>(polyTuple).evaluate() == 5. );
    assert( std::get<1>(polyTuple).evaluate() == 7. );
    assert( std::get<2>(polyTuple).evaluate() == 5. );

// test polymorphic access, indiraction 
    constexpr auto check = polyArrayPtr.size();

    assert( polyArrayPtr[0]->evaluate() == 5. );
    assert( polyArrayPtr[1]->evaluate() == 7. );
    assert( polyArrayPtr[2]->evaluate() == 5. );
}

Upvotes: 1

alfC
alfC

Reputation: 16300

I am not an expert in constexpr. But intuition tells me your elements must be global and, for example, static to the function.

Since this is not possible in a constexpr function (the compiler tells me) I put them outside and the whole thing works.

#include<array>

struct AbstractS {};

template<typename T>
struct S : AbstractS {};

namespace detail{
  constexpr auto v1 = S<int>{};
  constexpr auto v2 = S<double>{};
}

constexpr auto createArray() {
    constexpr std::array<const AbstractS *, 2> values{ &detail::v1, &detail::v2 };
    return values;
}

constexpr void getArray() {
    constexpr auto values = createArray();
}

int main(){}

https://godbolt.org/z/1q6MbMhdM

Upvotes: 0

Related Questions