Преобразование из foo‹T› в const foo‹const T› - C++

У меня есть такая функция (пожалуйста, не беспокойтесь о временном возврате по ссылке. Это просто пример для объяснения проблемы),

const foo<const int>& get_const()
{
    foo<int> f;
    return f;
}

Это явно не скомпилируется. Я ищу способ гарантировать, что вызывающие абоненты не изменят T из foo. Как я могу это гарантировать?

Я видел подобное поведение для boost::shared_ptr. shared_ptr<T> конвертируется в const shared_ptr<const T>. Я не мог понять, как это делается.

Любая помощь будет здорово.


person Navaneeth K N    schedule 18.02.2010    source источник
comment
Вы, вероятно, пытаетесь гарантировать, что вызывающие не изменят f foo.   -  person Bruno Brant    schedule 18.02.2010


Ответы (4)


Компилятор видит foo<T> и foo<const T> как два совершенно разных и не связанных между собой типа, поэтому класс foo должен явно поддерживать это, как и любое другое преобразование. Если у вас есть контроль над классом foo, вам необходимо предоставить конструктор копирования или оператор неявного преобразования (или оба).

template<typename T>
class foo 
{
 public: 

   // Regular constructor
   foo(T t) : t(t) {}

   // Copy constructor (works for any type S convertable to T, in particular S = non-const T if T is const)
   // Remember that foo<T> and foo<S> are unrelated, so the accessor method must be used here
   template<typename S> foo (const foo<S>& copy) : t(copy.getT()) {}

   // Accessor
   T getT() const { return t; }

   // Conversion operator
   operator foo<const T> () const { return foo<const T>(t); }

 private:

   T t;
};
person Tyler McHenry    schedule 18.02.2010

Предполагая, что Foo определен примерно так:

template<typename T> class Foo
{
public:
    Foo(const T& value) : m_value(value) { }
    const T& getValue() const { return m_value; }
    void setValue(const T& value) { m_value = value; }
private:
    T m_value;
};

Затем, чтобы гарантировать, что клиенты Foo не изменят m_value (я предполагаю, что это то, что подразумевается под «я ищу способ гарантировать, что вызывающие абоненты не изменят T для foo»), вам нужно const- квалифицировать объект Foo, а не его параметр шаблона, т.е.

Foo<int> x(1);
x.setValue(2); // OK
const Foo<int> y(1);
y.setValue(2); // does not compile

Следовательно, ваша функция get_foo должна возвращать const Foo<T>&, а не const Foo<const T>&.

Вот полный компилируемый пример:

#include <iostream>

template<typename T> class Foo
{
public:
    Foo(const T& value) : m_value(value) { }
    const T& getValue() const { return m_value; }
    void setValue(const T& value) { m_value = value; }
private:
    T m_value;
};

template<class T> class Owner
{
public:
    Owner(const T& value) : m_foo(value) { }
    Foo<T>& getFoo() { return m_foo; }
    const Foo<T>& getConstFoo() const { return m_foo; }

private:
    Foo<T> m_foo;
};


int main(int argc, char** argv)
{
    Owner<int> x(1);
    x.getFoo().setValue(2);
    // x.getConstFoo().setValue(3); // will not compile
}
person Gareth Stockwell    schedule 18.02.2010
comment
Хотя это отличный ответ, бывают случаи, когда const foo<const T> действительно может быть тем, что нужно, особенно если T является типом указателя или если foo хранит T*. - person Tyler McHenry; 18.02.2010
comment
@Tyler Согласен, OP был несколько неясен - хотя он спрашивает, как преобразовать Foo‹T› в Foo‹const T›, он также заявляет, что я ищу способ гарантировать, что вызывающие абоненты не изменят T из foo, что это то, на что мой ответ направлен путем определения константы самого объекта Foo. - person Gareth Stockwell; 18.02.2010
comment
Спасибо за ответ. Я ищу то, что упомянул @Tyler. - person Navaneeth K N; 18.02.2010

Если я не ошибаюсь, реализация boost::shared_ptr имеет неявный конструктор, который принимает ссылку const T& в качестве аргумента, а затем использует const_cast в указателе RHS для удаления const, разрешая неявные преобразования между ними.

Что-то вроде этого:

shared_ptr(const shared_ptr<const T>& r) : ptr(const_cast<T*>(r.ptr)) {}

Это то, что вы ищете?

person Peter Alexander    schedule 18.02.2010
comment
const_cast == Отвлекающий маневр для меня :) ОП ищет что-то, что на самом деле делает обратное (что гораздо более естественно). - person Matthieu M.; 18.02.2010

Прежде всего, вы возвращаете локальный объект по ссылке... это нехорошо.

foo и foo — это два разных типа, поэтому вам придется написать код (конструкторы преобразования), чтобы явно преобразовать их.

Чтобы получить то, что вы хотели, подумайте об этом:

template <typename T>
struct foo {T* t;};

const foo<int>& get_const(const foo<int>& f) {
    return f;
}

foo<int> f;
const foo<int>& cf = get_const(f);
f.t = 0; // ok, f is not const
*cf.t = 0; // ok because cf.t is const but what cf.t points to is not
cf.t = 0; // compiler error cf.t is const and cannot be lvalue

foo<int>& cf = get_const(f); // compiler error, cannot convert non-const to const without const_cast

Если вы правильно выполнили инкапсуляцию и получили доступ только к членам с константными геттерами и неконстантными сеттерами, этого должно быть достаточно для вас. Помните, что если люди действительно хотят изменить ваш объект, они всегда могут использовать const_cast. Const-корректность предназначена только для обнаружения непреднамеренных ошибок.

person Shing Yip    schedule 18.02.2010