Почему вектор указателей не может быть записан в const-контур указателей const?
Тип vector<char *>
не конвертируется в const vector<const char*>
. Например, следующее приводит к ошибке компиляции:
#include <vector>
using namespace std;
void fn(const vector<const char*> cvcc)
{
}
int main()
{
vector<char *> vc = vector<char *>();
fn(vc);
}
Я понимаю, почему vector<char*>
не может быть преобразован в vector<const char*>
- дополнительные элементы типа const char *
могут быть добавлены к вектору, а затем они будут доступны как неконстантные. Однако, если сам вектор const, это не может произойти.
Моя лучшая догадка заключается в том, что это было бы безобидно, но компилятор не может сделать вывод, что это было бы безвредно.
Как это можно обойти?
Этот вопрос был предложен С++ FQA здесь.
Ответы
Ответ 1
void fn(const vector<const char*>)
Поскольку квалификатор верхнего уровня const
отбрасывается для типа функции, это (на сайте вызова) эквивалентно:
void fn(vector<const char*>)
Оба запроса запрашивают копию переданного вектора, поскольку контейнеры стандартной библиотеки следуют семантике значений.
Вы можете:
- вызовите его через
fn({vc.begin(), vc.end()})
, запросив явное преобразование
- изменить подпись, например,
void fn(vector<const char*> const&)
, т.е. взяв ссылку
Если вы можете изменить подпись fn
, вы можете следовать GManNickG советам и использовать итераторы/a:
#include <iostream>
template<typename ConstRaIt>
void fn(ConstRaIt begin, ConstRaIt end)
{
for(; begin != end; ++begin)
{
std::cout << *begin << std::endl;
}
}
#include <vector>
int main()
{
char arr[] = "hello world";
std::vector<char *> vc;
for(char& c : arr) vc.push_back(&c);
fn(begin(vc), end(vc));
}
Это дает красивый вывод
hello world
ello world
llo world
lo world
o world
world
world
orld
rld
ld
d
Основная проблема заключается в том, чтобы обходить контейнеры стандартной библиотеки. Если вам нужен только постоянный доступ к данным, вам не нужно знать фактический тип контейнера и вместо этого использовать шаблон. Это удаляет связь fn
с типом контейнера, который использует вызывающий.
Как вы заметили, это плохая идея разрешить доступ к std::vector<T*>
через std::vector<const T*>&
. Но если вам не нужно изменять контейнер, вы можете использовать диапазон.
Если функция fn
не должна быть или не может быть шаблоном, вы все равно можете перемещаться вокруг диапазонов const char*
вместо векторов const char
. Это будет работать с любым контейнером, который гарантирует непрерывное хранение, например, необработанные массивы, std::array
s, std::vector
и std::string
s.
Ответ 2
В общем, С++ не позволяет отбрасывать someclass<T>
в someclass<U>
, поскольку шаблон someclass
может быть специализирован для U
. Неважно, как связаны T
и U
. Это означает, что реализация и, следовательно, макет объекта могут быть разными.
К сожалению, нет способа рассказать компилятору, в каких случаях макет и/или поведение не менялись, и приведение было принято. Я предполагаю, что это может быть очень полезно для std::shared_ptr
и других прецедентов, но это не произойдет в ближайшее время (AFAIK).
Ответ 3
Чтобы обойти эту проблему, вы можете реализовать свою собственную оболочку шаблона:
template <typename T> class const_ptr_vector;
template <typename T> class const_ptr_vector<T *> {
const std::vector<T *> &v_;
public:
const_ptr_vector (const std::vector<T *> &v) : v_(v) {}
typedef const T * value_type;
//...
value_type operator [] (int i) const { return v_[i]; }
//...
operator std::vector<const T *> () const {
return std::vector<const T *>(v_.begin(), v_.end());
}
//...
};
Идея заключается в том, что оболочка предоставляет интерфейс к указанному вектору, но возвращаемые значения всегда const T *
. Поскольку ссылочный вектор const
, все интерфейсы, предоставляемые оберткой, также должны быть const
, как показано оператором []
. Сюда входят итераторы, которые будут предоставлены оболочкой. Итераторы-обертки просто содержат итератор к указанному вектору, но все операции, которые дают значение указателя, будут const T *
.
Цель этого обходного пути заключается не в предоставлении того, что может быть передано функции, требующей const std::vector<const T *> &
, а для предоставления другого типа для использования для функции, обеспечивающей безопасность типа такого вектора.
Пока вы не могли передать это функции, ожидающей const std::vector<const T *> &
, вы могли бы реализовать оператор преобразования, который вернул бы копию std::vector<const T *>
, инициализированную значениями указателя из базового вектора. Это также было проиллюстрировано в приведенном выше примере.
Ответ 4
Как предполагает FQA, это фундаментальный недостаток в С++.
Кажется, что вы можете сделать то, что хотите, с помощью какого-то явного каста:
vector<char*> vc = vector<char *>();
vector<const char*>* vcc = reinterpret_cast<vector<const char*>*>(&vc);
fn(*vcc);
Это вызывает поведение Undefined и не гарантирует работу; однако я почти уверен, что он будет работать в gcc с отключенным строгим псевдонимом (-fno-strict-aliasing
). В любом случае это может работать только в качестве временного взлома; вы должны просто скопировать вектор, чтобы сделать то, что вы хотите, гарантированным образом.
std::copy(vc.begin(), vc.end(), std::back_inserter(vcc));
Это также хорошо с точки зрения производительности, потому что fn
копирует свой параметр при его вызове.