распространение_конста const unique_ptr - PullRequest
4 голосов
/ 15 марта 2020

Следующий пример взят из cppreference :

#include <iostream>
#include <memory>
#include <experimental/propagate_const>

struct X
{
    void g() const { std::cout << "g (const)\n"; }
    void g() { std::cout << "g (non-const)\n"; }
};

struct Y
{
    Y() : m_ptrX(std::make_unique<X>()) { }

    void f() const
    {
        std::cout << "f (const)\n";
        m_ptrX->g();
    }

    void f()
    {
        std::cout << "f (non-const)\n";
        m_ptrX->g();
    }

    std::experimental::propagate_const<std::unique_ptr<X>> m_ptrX;
};

int main()
{
    Y y;
    y.f();

    const Y cy;
    cy.f();
}

Я хочу дополнительно убедиться, что адрес указателя (m_ptrX) не может быть изменен, поэтому я изменил объявление на

std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;

Но это не работает, g cc 9 сообщает о следующей ошибке (подробности см. здесь )

g++ -std=c++2a -pthread  -O2 -Wall -Wextra -pedantic -pthread -pedantic-errors main.cpp -lm  -latomic -lstdc++fs  && ./a.out

In file included from main.cpp:3:

/usr/local/include/c++/9.2.0/experimental/propagate_const: In instantiation of 'constexpr std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type* std::experimental::fundamentals_v2::propagate_const<_Tp>::get() [with _Tp = const std::unique_ptr<X>; std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type = X]':

/usr/local/include/c++/9.2.0/experimental/propagate_const:205:13:   required from 'constexpr std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type* std::experimental::fundamentals_v2::propagate_const<_Tp>::operator->() [with _Tp = const std::unique_ptr<X>; std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type = X]'

main.cpp:24:15:   required from here

/usr/local/include/c++/9.2.0/experimental/propagate_const:225:25: error: invalid conversion from 'const element_type*' {aka 'const X*'} to 'std::experimental::fundamentals_v2::propagate_const<const std::unique_ptr<X> >::element_type*' {aka 'X*'} [-fpermissive]

  225 |  return __to_raw_pointer(_M_t);

      |         ~~~~~~~~~~~~~~~~^~~~~~

      |                         |

      |                         const element_type* {aka const X*}

Так что же правильно способ достижения эффекта, при условии, что я не хочу реализовывать шаблон как immutable_unique_ptr.

Ответы [ 2 ]

4 голосов
/ 17 марта 2020

Кажется невозможным защитить std::unique_ptr от модификации, сделав его const в std::experimental::propagate_const, но чтобы понять почему, нам нужно go через исходный код std::experimental::propagate_const, он доступен на пропагат_конст .

propagate_const имеет закрытую переменную,

private:
      _Tp _M_t;

Поэтому, когда вы создаете объект std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;, здесь _Tp является параметром шаблона, и это приведет к _Tp = const std::unique_ptr<X>

Теперь об ошибке сообщается в m_ptr->g(); строке void Y::f(), неконстантная перегрузка f, поэтому давайте go пройдем через все вызовы функций propagate_const.

Итак, m_ptr->g() делает вызов неконстантным operator->() из propagate_const и который имеет следующую реализацию,

constexpr element_type* operator->(){
    return get();
}

Затем он вызывает get(), неконстантную перегрузку, потому что operator->() само по себе не является постоянной перегрузкой, теперь get() имеют следующую реализацию,

constexpr element_type* get(){
    return __to_raw_pointer(_M_t);
}

Наконец, __to_raw_pointer является закрытой функцией шаблона propagate_const и имеет следующие перегрузки,

template <typename _Up> static constexpr element_type* __to_raw_pointer(_Up* __u) { return __u; }

template <typename _Up> static constexpr element_type* __to_raw_pointer(_Up& __u) { return __u.get(); }

template <typename _Up>static constexpr const element_type* __to_raw_pointer(const _Up* __u) { return __u; }

template <typename _Up> static constexpr const element_type* __to_raw_pointer(const _Up& __u) { return __u.get(); }

Поскольку тип закрытого элемента данных _M_t равен const std::unique_ptr<X> и из-за этого __to_raw_pointer(_M_t) выберет последнюю перегрузку, то есть

template <typename _Up> 
static constexpr const element_type* __to_raw_pointer(const _Up& __u) { 
     return __u.get(); 
}

Так что это источник ошибки, тип возвращаемого значения const element_type*, который будет выведен на const X*, но не постоянная перегрузка get()

constexpr element_type* get() { 
return __to_raw_pointer(_M_t); 
}

имеет тип возврата element_type*, который будет выведен на X*. Понятно, что const X* не может конвертироваться в X*, и это именно та ошибка, о которой сообщают.

Так что std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX; не будет работать.

2 голосов
/ 17 марта 2020

ошибка tldr

Основы библиотеки TS v2 говорят об этом как о неконстантном propagate_const<T>::operator-> (element_type is X):

  1. constexpr element_type* operator->()
  2. Требуется: get() != nullptr
  3. Возвращает: get()

Указывается в терминах неконстантных get, который указан так:

constexpr element_type* get(); Возвращает: t_, если T является типом указателя объекта, в противном случае t_.get().

Это совершенно верно, даже если T является постоянным.

Мы могли бы искать требования к самому типу. TS устанавливает требования к аргументу шаблона в propagate_const в [пропагат_конст.requirements], [пропагат_конст.class_type_requirements] и в таблицу 4.

Все эти требования выполнены или не применяются. Квалификация cv T не требуется.

Самое интересное предложение, которое я могу найти, - это утверждение [пропагат_const.class_type_requirements] / 1:

В этом подпункте -clause t обозначает неконстантное значение типа T, ct обозначает const T&, связанный с t, element_type обозначает тип объекта.

Это предложение является предметом выпуска LWG 3136 , но это не помогает осветить его, за исключением того, что подразумевается, что оно не было написано в типичном стиле или написано небрежно

Можно, вероятно, утверждать, что это предложение неявно требует, чтобы была возможность иметь неконстантное значение типа T и, следовательно, T не может быть константой. Это кажется напряженным. Предложение применяет ограничения к неконстантному lvalue типа T и не требует, чтобы такие существовали. Однако он также применяет ограничения на ссылку на const, привязанную к неконстантному lvalue, в отличие от lvalue типа const T. Поэтому требования ct также не применяются, что абсурдно. Итак, мой вывод заключается в том, что это небрежно сформулировано, и это предложение не является неявно запрещающим const T. Неконстантные требования не должны применяться.

...