首页 > 解决方案 > 我们可以在 constexpr 函数中创建一个指向对象的指针的 std::array 并返回它吗?

问题描述

假设我们有某种模板结构:

template<typename T>
struct S {};

我们想创建一个 S 类型对象的 std::array。但是,因为 S 是一个模板结构,我们需要创建一个基类来保存这些对象。

struct AbstractS {};

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

现在,假设我们有一个创建 std::array 并返回它的 constexpr 函数和一个用于获取该数组的 constexpr 函数。

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();
}

此代码无法编译,我认为这是因为 v1 和 v2 的地址不是常量。

让我给你一个具体的例子来说明我正在尝试做的事情。

struct AbstractPolynomial {};

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

我有一个对多项式函数进行建模的结构,其中 T 是多项式系数的值的类型,而度数是多项式度数。

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;
}

假设多项式有一个推导指南(我这里没有实现)。我知道您无法获得临时地址并且分配线路不正确,但我输入此表格是因为我希望您为我提供此方案的解决方案。

我们可以做一些技巧来创建这样的场景吗?

标签: c++pointerspolymorphismconstexprcompile-time

解决方案


这是解决方案,请注意,多项式需要在最初生成时保留其编译时类型。出于任何原因(动态访问)不使用该信息的后一种选择是您的。

注意数据结构是 constexpr,但不一定是多项式的求值。我认为这是因为virtual干扰了函数的constexpr-ness 能力。evaluate

您可以在此处使用解决方案: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. );
}

推荐阅读