Вектор и 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