Вектор и const

Рассмотрим это

 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
 }

Не компилируется следующее. Дело в том, что vector<T*> не может быть преобразовано в vector <const T*>, и это кажется мне нелогичным, потому что существует неявное преобразование от T* до const T*. Почему это?

vector<const T*> тоже нельзя преобразовать в vector <T*>, но это ожидается, потому что const T* не может быть преобразован неявно в T*.

Ответ 1

Я добавил несколько строк к вашему коду. Этого достаточно, чтобы дать понять, почему это запрещено:

void f(vector<const T*>& p)
 {
    static const T ct;
    p.push_back(&ct); // adds a const T* to nonConstVec !
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
  nonConstVec.back()->nonConstFunction();
 }

Ответ 2

vector<T> и vector<const T> являются несвязанными типами. Тот факт, что T можно преобразовать в const T, здесь не означает.

Вы должны думать об этом с точки зрения системы типов. Созданный vector<int> не имеет ничего общего с vector<const int>.

Ответ 3

Возможно, стоит показать, почему это нарушение const-correctness для выполнения требуемого преобразования:

#include <vector>
const int a = 1;

void addConst(std::vector<const int *> &v) {
    v.push_back(&a); // this is OK, adding a const int* to a vector of same
}

int main() {
    std::vector<int *> w;
    int b = 2;
    w.push_back(&b);  // this is OK, adding an int* to a vector of same
    *(w.back()) = 3;  // this is OK, assigning through an int*
    addConst(w);      // you want this to be OK, but it isn't...
    *(w.back()) = 3;  // ...because it would make this const-unsafe.
}

Проблема заключается в том, что vector<int*>.push_back принимает указатель на не-const (который я буду называть "неконстантным указателем" с этого момента). Это означает, что он может изменить пункт его параметра. В частности, в случае вектора он может передать указатель обратно кому-то другому, который его модифицирует. Таким образом, вы не можете передать указатель const на функцию push_back из w, а преобразование, которое вы хотите, небезопасно, даже если система шаблонов поддерживала его (а это не так). Цель const-безопасности - остановить передачу указателя const на функцию, которая принимает указатель не const, и именно так он выполняет свою работу. С++ требует от вас особо сказать, хотите ли вы сделать что-то небезопасное, поэтому преобразование, конечно, не может быть имплицитным. Фактически, из-за того, как работают шаблоны, это вообще невозможно (см. Ниже).

Я думаю, что С++ может в принципе сохранить const-безопасность, разрешив преобразование от vector<T*>& до const vector<const T*>&, так же, как int ** to const int *const * безопасно. Но это из-за того, как определяется вектор: он не обязательно будет const-safe для других шаблонов.

Аналогично, теоретически это позволяет явное преобразование. И фактически, он допускает явное преобразование, но только для объектов, а не ссылок; -)

std::vector<const int*> x(w.begin(), w.end()); // conversion

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

template<typename T> 
struct Foo {
    void Bar(T &);
};

template<>
struct Foo<const int *> {
    void Baz(int *);
};

Теперь Foo<int*> не имеет функции Baz. Как можно было бы преобразовать указатель или ссылку на Foo<int*> в указатель или ссылку на Foo<const int*>?

Foo<int *> f;
Foo<const int *> &g = f; // Not allowed, but suppose it was
int a;
g.Baz(&a); // Um. What happens? Calls Baz on the object f?

Ответ 4

Подумайте вот так:

У вас есть два класса:

class V  { T*       t;};
class VC { T const* t;};

Вы ожидаете, что эти два класса будут автоматически конвертированы?
В основном это класс шаблонов. Каждый вариант является совершенно новым типом.

Таким образом, вектор < T * > и вектор < T const * > являются совершенно разными типами.

Мой первый вопрос: действительно ли вы хотите хранить указатели?

Если да, я бы предложил посмотреть на boost:: ptr_container. Это удерживает указатели и удаляет их, когда вектор уничтожается. Но что более важно, он рассматривает содержащиеся указатели как обычный std: vector обрабатывает содержащиеся в нем объекты. Таким образом, создавая вектор const, вы можете обращаться к своим членам только как const

void function(boost::ptr_vector<T> const& x)
{
     x.push_back(new T);  // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

Если вам не нужно сохранять указатели, тогда сохраните объекты (а не указатели) в контейнере.

void function(std::vector<T> const& x)
{
     x.push_back(T());    // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

Ответ 5

Другие уже дали причину, почему код, который вы дали, не компилируется, но у меня есть другой ответ о том, как с этим бороться. Я не верю, что есть способ научить компилятора, как автоматически преобразовать два (потому что это связано с изменением определения std::vector). Единственный способ обойти это раздражение - сделать явное преобразование.

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

#include <vector>
#include <iostream>

using namespace std;

typedef int T;

T a = 1;
T b = 2;

void f(vector<const T*>& p)
{
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) {
        cout << **iter << endl;
    }
}
vector<const T*>& constify(vector<T*>& v)
{
  // Compiler doesn't know how to automatically convert
  // std::vector<T*> to std::vector<T const*> because the way
  // the template system works means that in theory the two may
  // be specialised differently.  This is an explicit conversion.
  return reinterpret_cast<vector<const T*>&>(v);
}
int main()
{
  vector<T*> nonConstVec;
  nonConstVec.push_back(&a);
  nonConstVec.push_back(&b);
  f(constify(nonConstVec));
}

Я использую reinterpret_cast, чтобы объявить, что две вещи одинаковы. Вы ДОЛЖНЫ чувствовать себя грязным после использования, но если вы поместите его в функцию самостоятельно с комментарием для тех, кто следит за вами, тогда вымойте и попытайтесь продолжить свой путь с хорошей совестью, хотя вы всегда (по праву) будете что нытье беспокоиться о том, кто-то вытащил землю из-под вас.

Ответ 6

Как говорили другие, конверсии не применяются к параметрам шаблона. Другими словами,

vector<T>

... и:

vector<const T>

... - совершенно разные типы.

Если вы пытаетесь реализовать const-correctness относительно f(), не изменяя содержимое вектора, это может быть больше в соответствии с тем, что вы ищете:

void f(vector<T>::const_iterator begin, vector<T>::const_iterator end)
{
  for( ; begin != end; ++begin )
  {
    // do something with *begin
  }
}

int main()
{
  vector<T> nonConstVec;
  f(nonConstVec.begin(), nonConstVec.end());
}

Ответ 7

То, как работают шаблоны - никаких преобразований не применяются к параметрам шаблона, поэтому два вектора имеют совершенно разные типы.

Ответ 8

Шаблоны немного странны. Тот факт, что там неявное преобразование из T в U не означает, что существует неявное преобразование с XXX на XXX. Это может произойти, но в коде шаблона требуется довольно много дополнительной работы, и, судя по всему, я сомневаюсь, что все методы были известны при разработке std::vector (точнее, я довольно что они не были известны).

Изменить: такие проблемы являются частью мотивации использования итераторов. Хотя container of X неявно не конвертируется в container of const X, a container<X>::iterator неявно конвертируется в container<X>::const_iterator.

Если вы замените свой:

void f(vector<const T*>& p) {}

с:

template <class const_iter>
void f(const_iter b, const_iter e) {}

Тогда:

int main() { 
    vector<T*> nonConstVec;
    f(nonConstVec.begin(), nonConstVec.end());
    return 0;
}

будет просто отлично - и так будет:

vector<T const *> constVec;
f(constVec.begin(), constVec.end());

Ответ 9

Оба vector<const T*> и vector<T*> являются совершенно разными типами. Даже если вы пишете const T* внутри своего main(), ваш код не будет компилироваться. Вам необходимо предоставить специализацию внутри основного.

Следующие компиляции:

 #include<vector>
 using namespace std;

 template<typename T>
 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
     vector<const int*> nonConstVec;
     f(nonConstVec);
 }

Ответ 10

в дополнение к другим ответам, стоит прочитать С++ FQA Lite, где это (и многие другие функции С++) обсуждаются с критического POV: http://yosefk.com/c++fqa/const.html#fqa-18.1