Можем ли мы создать массив std::указателей на объект в функции constexpr и вернуть его?

#c #pointers #polymorphism #constexpr #compile-time

Вопрос:

Предположим, у нас есть какая-то шаблонная структура:

 template<typename T>
struct S {};
 

Мы хотим создать массив std::некоторого объекта типа S. Но, поскольку S-это структура шаблона, нам нужно создать базовый класс для хранения этих объектов.

 struct AbstractS {};

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

Теперь предположим, что у нас есть функция constexpr, которая создает массив std::и возвращает его, а также функция constexpr для получения этого массива.

 constexpr auto createArray() {
    constexpr auto v1 = S<int>{};
    constexpr auto v2 = S<double>{};
    std::array<const AbstractS *, 2> values{ amp;v1, amp;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] = amp;Polynomials<T1>{5};
        } else {
            polynomials[i] = amp;Polynomials<T2>{2, 5};
        }
    }
    return polynomials;
}
 

Предположим, что у этого полинома есть руководство по дедукции (я не реализовал его здесь). Я знаю, что вы не можете получить временный адрес и строки назначения неверны, но я ввел эту форму, потому что хочу, чтобы вы дали мне решение для этого сценария.

Можем ли мы проделать какие-нибудь трюки, чтобы создать подобный сценарий?

Комментарии:

1. вернуть а std::tuple<S<int>, S<double>> ?

2. Я хочу что-то более общее, потому что у нас может быть более 2 типов.

3. Какую ошибку компилятора вы получаете?

4. std::массив<…> не является постоянным выражением. @alfC

5. std::кортеж может содержать более 2 элементов…

Ответ №1:

Это решение, обратите внимание, что полиномы должны быть сгенерированы изначально с сохранением их типа во время компиляции. Более поздний выбор не использовать эту информацию по какой-либо причине (динамический доступ) остается за вами.

Обратите внимание, что структура данных является constexpr, но не обязательно является оценкой полинома. Я думаю, что это происходит потому, что мешает возможностям функции. virtual constexpr 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 constamp;... e){return std::array<AbstractPolynomial const*, std::tuple_size<decltype(polyTuple)>{}>{amp;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. );
}
 

Комментарии:

1. Спасибо, @alfC . Ваше решение блестящее, мне нужно изучить std::index_sequence, потому что я не знаю, как на самом деле работает, но спасибо, я постараюсь использовать это 🙂

2. @apopa index_sequence задокументирован в cppreference.com. Не стесняйтесь принять или озвучить ответ, который вам больше всего нравится.

3. Я не могу озвучить ваш ответ прямо сейчас, потому что мой аккаунт почти новый, и у меня всего 11 очков. Когда я наберу больше очков, я поддержу ваш ответ. Еще раз спасибо.

Ответ №2:

Я не специалист в constexpr этом . Но интуиция подсказывает мне, что ваши элементы должны быть глобальными и, например, статичными для функции.

Так как это невозможно в constexpr функции (компилятор говорит мне) Я выставляю их на улицу, и все это работает.

 #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{ amp;detail::v1, amp;detail::v2 };
    return values;
}

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

int main(){}
 

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

Комментарии:

1. Да, я знаю, что элементы могут быть глобальными, и программа компилируется, но мне не нужны глобальные переменные. Предположим, у нас есть сотни значений. @alfC

2. @apopa, 1) статические переменные являются глобальными переменными. 2) если они генерируются динамически (потому что их сотни), то они не могут быть constexpr. Без дополнительного контекста я не могу вам помочь. Не говоря уже о том, что хранение указателя базового класса на самом деле не является решением, потому что вам нужно где-то хранить точный тип экземпляра. Я думаю, что вы ищете std::tuple<S<int> , S<double> , ... > .

3. Предположим, у вас есть такой сценарий: шаблон<std::size_t N> constexpr auto f() { std::массив<std::size_t N><int, N> a; для (int i = 0; i <int, N> @alfC

4. @apopa, Если ваши сгенерированные значения constexpr параметризованы/сгенерированы целым числом, вы можете попробовать std::index_sequence . Для этого требуется немного метапрограммирования, но я не понимаю, что вы пытаетесь сделать.

5. @apopa, что бы ты ни пытался сделать. Сначала сделайте это без constexpr, и сделайте это правильно, чтобы показать, чего вы хотите достичь.