Почему вектор указателей не может быть записан в 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 копирует свой параметр при его вызове.