Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

propagate_const of const unique_ptr

The following example is taken from 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();
}

I want to further ensure the pointer (m_ptrX) address is not modifiable, so I changed the declaration to

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

But it doesn't work, gcc 9 reports the following error (see here for detail)

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*}

So what is the correct way to achieve the effect, assuming I don't want to implement a template like immutable_unique_ptr.

like image 869
Kan Li Avatar asked Mar 15 '20 04:03

Kan Li


People also ask

Is propagate_const moveconstructible or CopyConstructible?

It treats the wrapped pointer as a pointer to const when accessed through a const access path, hence the name. The class satisfies the requirements of MoveConstructible and MoveAssignable if the underlying pointer-like type satisfies the corresponding requirement, but propagate_const is neither CopyConstructible nor CopyAssignable .

How to init a collection of unique_ptr on the stack?

If you want to init a const collection of unique_ptr on the stack: const std::array<> - might be ok, but you need to specify the template parameters as the compiler cannot deduce the pointer to the base class from the derived objects using std::vector might also be fine, but you’ll pay extra cost for a memory allocation

How to efficiently create new instances of unique_ptr in Java?

Use the make_unique helper function to efficiently create new instances of unique_ptr. unique_ptr uniquely manages a resource. Each unique_ptr object stores a pointer to the object that it owns or stores a null pointer.

How to change APTR without unique_ptr in a class?

Without the unique_ptr, the solution is to have void takePtr (AClass const * const aPtr) { // Do something with *aPtr. // We cannot change aPtr, not *aPtr. } (Well, technically, AClass const * aPtr is enough.)


2 Answers

It seems not possible to protect std::unique_ptr against modification by making it const in std::experimental::propagate_const, but to understand why, we have to go through source code of std::experimental::propagate_const, it is available at propagate_const .

propagate_const have a private variable,

private:
      _Tp _M_t;

So when you create object std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;, here _Tp is template parameter and it will deduce to _Tp = const std::unique_ptr<X>

Now error reported at m_ptr->g(); line of void Y::f(), non const overload of f, so let's go through all function calls of propagate_const.

So m_ptr->g() make call to non const operator->() of propagate_const and which have following implementation,

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

It then calls to get(), non const overload because operator->() it self is non const overload, Now get() have following implementation,

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

Finally __to_raw_pointer is private template function of propagate_const and have following overloads,

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(); }

Because type of private data member _M_t is const std::unique_ptr<X> and due to this __to_raw_pointer(_M_t) will select last overload and that is,

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

So this is source of error, return type is const element_type* which will deduced to const X* but non const overload of get()

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

have return type element_type* which will deduced to X*.
It's clear now const X* can not convert to X* and that is exactly the reported error.

So std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX; will not work.

like image 191
Vikas Awadhiya Avatar answered Oct 05 '22 21:10

Vikas Awadhiya


tldr bug

The Library Fundamentals TS v2 says this about non-const propagate_const<T>::operator-> (element_type is X):

  1. constexpr element_type* operator->()
  2. Requires: get() != nullptr
  3. Returns: get()

It is specified in terms of non-const get, which is specified thusly:

  1. constexpr element_type* get();
  2. Returns: t_ if T is an object pointer type, otherwise t_.get().

This is perfectly valid even if T is const.

We could look for requirements on the type itself. The TS places requirements on the template argument to propagate_const in [propagate_const.requirements], [propagate_const.class_type_requirements], and in Table 4.

All of these requirements are met or do not apply. There is no requirement on T's cv qualification.

The most interesting sentence I can find is in the statement [propagate_const.class_type_requirements]/1:

In this sub-clause t denotes a non-const lvalue of type T, ct is a const T& bound to t, element_type denotes an object type.

This sentence is the subject of LWG issue 3136, but that does not help illuminate it except to imply that it was maybe not drafted in typical style or is drafted sloppily

One could perhaps argue that that this clause is implicitly requiring that it be possible to have a non-const lvalue of type T and thus T cannot be const. That seems strained. The clause is applying constraints on a non-const lvalue of type T and not requiring that any such exist. However, it also is applying constraints on a reference to const bound to a non-const lvalue, as opposed to an lvalue of type const T. Therefore, the ct requirements also do not apply, which is absurd. So, my conclusion is that this is sloppily worded and this sentence is not implicitly forbidding const T. The non-const requirements should be not applicable.

like image 33
Jeff Garrett Avatar answered Oct 05 '22 22:10

Jeff Garrett