Очень часто в C++ мне нужен контейнер STL типа, который имеет ссылочный элемент:
struct C {};
struct S
{
S() = default;
S(const C& c) : _c(c)
{}
const C& _c; // I always have to use a pointer here
};
int main()
{
C c;
std::array<S, 10> arr{ c }; // Can c be passed here?
return 0;
}
Однако, поскольку ссылки должны быть установлены с самого начала, я никогда не смогу заставить это работать, и мне придется использовать указатель (что мне очень не нравится).
Вышеупомянутое не компилируется.
error: no matching constructor for initialization of 'S'
Можно ли передать ссылку через конструктор std::array
и не использовать указатель?
S() = default;
«неправильный» (фактически удален).
При правильном номере инициализатора все работает Демо.
Ссылочный член делает класс нестандартным для конструирования и теряет также некоторые другие свойства, что затрудняет использование класса.
Почему бы не использовать член-указатель?
@Jarod42 Есть ли элегантный способ реализовать ваше решение для 1000 элементов массива?
std::array<S, 1000> arr{c,c,c,c,c,c,c,c,c,c,c,c,c,c,
продолжайте повторять c,
, чтобы заполнить все 1000 элементов — это элегантное решение.
Напишите make_array
Посмотрите еще один ответ. Обратите внимание, что вам придется вызывать его с помощью S{c}
.
Из приведенного примера вы можете сделать _c
static
. (Также переименуйте, чтобы не было подчеркивания, поскольку все переменные, начинающиеся с _
, зарезервированы для стандартной библиотеки.)
Если нет, вы, вероятно, ищете:
std::array<S, 10> array{S{c}};
Здесь 10 S
построены из одинаковых c
Если сделать S::c
статическим, то его придется инициализировать раньше main
... И изменится семантика класса.
S{c}
не был нужен, S(const C&)
подразумевается. Но он создаст только первый элемент, проблема связана с остальными 9 объектами, которые не могут быть инициализированы по умолчанию.
Изменение c
на static
— это изменение дизайна. В вопросе нет ничего, что указывало бы на то, что это уместно.
Также переименуйте, чтобы не было подчеркивания, поскольку все переменные, начинающиеся с _
, зарезервированы для стандартной библиотеки. это неверно. Это верно только в глобальном масштабе. В любой области действия, начиная с _
, за которым следуют прописные буквы, зарезервировано; _
, за которым следует строчная буква, не зарезервирована и может использоваться свободно (за пределами глобальной области видимости). Кроме того, двойное подчеркивание __
в любом месте идентификатора также зарезервировано в любой области действия.
Нет, имена, начинающиеся с подчеркивания, не зарезервированы для стандартной библиотеки. Имена, начинающиеся с подчеркивания, за которым следует заглавная буква, зарезервированы для использования реализацией. Имена, начинающиеся с подчеркивания, за которым следует строчная буква, зарезервированы для использования реализацией в глобальной области видимости. Нет ничего плохого (кроме того, что <g> выглядит глупо) в их использовании в области блока.
В вашем коде есть 2 проблемы:
Вы не можете сделать конструктор по умолчанию дефолтным с помощью:
S() = default;
Потому что ссылка на член _c
должна быть инициализирована.
arr
следует инициализировать 10 элементами (поскольку S
нельзя сконструировать по умолчанию, как описано выше):
std::array<S, 10> arr{c, c, c, c, c, c, c, c, c, c};
Можно ли передать ссылку через конструктор
std::array
и не использовать указатель?[...] продолжайте повторять
c
, как заполнить все элементы1000
— это элегантное решение?
Если вам не нравятся указатели, я бы предложил использовать std::reference_wrapper вместо ссылок на плоскости. Почему:
В чем разница между std::reference_wrapper и простым указателем?
Преимущества использования reference_wrapper вместо необработанного указателя в контейнерах?
#include <functional> // std::reference_wrapper
struct S
{
constexpr explicit S(C& c)
: _c{c} {}
private:
std::reference_wrapper<C> _c;
};
Теперь, чтобы заполнить массив (в C++20), вы можете написать помощник make_ref_array()
следующим образом:
template <size_t N>
constexpr auto make_ref_array(C& refArg)
{
return [&refArg]<size_t... Is>(std::index_sequence<Is...>) {
return std::array<S, sizeof...(Is)>{(static_cast<void>(Is), S{ refArg })...};
}(std::make_index_sequence<N>{});
}
так что создание массива будет похоже на
C c;
auto arr = make_ref_array<100u>(c);
Теперь это может быть более общим, например:
template <typename T, size_t N, typename... Args>
constexpr auto make_ref_array(Args&... args)
{
return []<size_t... Is, typename... Ts>(std::index_sequence<Is...>, Ts&... ts) {
return std::array<T, sizeof...(Is)>{(static_cast<void>(Is), T{ ts... })...};
}(std::make_index_sequence<N>{}, args...);
}
Обратите внимание, что для построения args
будут использоваться все T
, даже если другие элементы данных не являются ссылками.
Для компиляторов старше C++20 лямбда-выражение шаблона необходимо переместить в отдельную функцию шаблона, как , как в этом примере
Спасибо тебе за это. Я просто..... озадачен, почему C++ не может сделать это «под капотом». Когда кто-то пишет std::array<S, 1000> arr{c};
, он явно хочет передать всем 1000 элементам! Никто не инициализирует только один элемент массива.
Вас может заинтересовать std::reference_wrapper и связанная с ней вспомогательная функция std::ref.