1

When I write a class like this static_cast() calls the custom conversion operator. But static_pointer_cast() does not compile. Why is that, and what do I need to do to make it work?

class A{
   //class implementation
   operator int(){
      return 42;
   }
}

A a();
int i = static_cast<int>(a); //works i = 42

std::shared_ptr<A> pa = std::make_shared<A>();
std::shared_ptr<int> pi = static_pointer_cast<int>(pa); //does not compile

Edit: Ultimately I need to convert smart_ptr of templated class, where the templated argument is a derived class, e.g.:

template<typename T>
class Foo; 

class Base; 

class Derived: public Base{}

shared_ptr<Foo<Derived>> fb = make_shared<Foo<Derived>>();

shared_ptr<Foo<Base>> x = static_pointer_cast<Foo<Base>>(fb);
flxh
  • 565
  • 4
  • 19
  • Does your compiler support the C++20 standard? This requires C++20. – Sam Varshavchik Apr 22 '22 at 15:07
  • 6
    You are trying to convert a `A*` to an `int*`, which will never work in a static cast. You can cast an `A` to an `int` because you've defined an operator for that in `A`. – NathanOliver Apr 22 '22 at 15:09
  • @SamVarshavchik So you are saying this would work with C++20 ? Do I need to implement anything additionally or does the compiler know what to do implicitly? – flxh Apr 22 '22 at 15:17
  • @NathanOliver Yes I thought maybe there is a custom conversion function for this too ... basically converting the type and returning the pointer, no? – flxh Apr 22 '22 at 15:18
  • @flxh No, that's not what the cast does. Essentially what you are trying to do is `int* i_ptr = static_cast(a_ptr);` which will never work. – NathanOliver Apr 22 '22 at 15:30
  • 4
    `Foo` and `Foo` are unrelated classes. While `Derived` is derived from `Base`, this is not true for `Foo` and `Foo`. This is actually a well-known problem for `std::vector`s when it contains elements of one type where elements of a compatible type are needed. Finally, element-wise conversion is the only clean way. I.e. in your case: you may cast the contents but you may not cast the smart-pointer. – Scheff's Cat Apr 22 '22 at 15:34
  • the `std::shared_ptr` doesn't point to any dynamically allocated resource. Not sure what you think it would do? – apple apple Apr 22 '22 at 15:38
  • @Scheff'sCat Exactly and because they are unrelated I implemented a custom conversion function. I hoped static_pointer_cast can make use of a custom conversion function just like static_cast ? – flxh Apr 22 '22 at 15:41
  • Please note, that doesn't help. In my example, I explicitly mentioned _where elements of a **compatible** type are needed_. ;-) Your conversion produces an RValue which doesn't have an address. Hence, that conversion of values is possible doesn't imply that conversion of the corresponding pointers is supported. A simple example: `double a = 12.3;` `(int)a;` is fully OK but `(int*)&a` is Undefined Behavior. – Scheff's Cat Apr 22 '22 at 15:47
  • @flxh `static_cast(a)` works because you are converting an **object** to an `int`, and you defined that conversion. Whereas `static_pointer_cast(pa)` does NOT work because you are trying to convert a `shared_ptr` holding an `A*` **pointer** to a new `shared_ptr` holding an `int*` **pointer**, it is right there in the name - `static POINTER cast` - and there is no conversion defined from `A*` to `int*`, nor can you ever define such a conversion. – Remy Lebeau Apr 22 '22 at 16:35

1 Answers1

1

does not compile. Why is that

std::static_pointer_cast is for conversions between pointer types. The corresponding bare pointer cast would be:

A* a_ptr = &a;
int* i_ptr = static_cast<int*>(a_ptr);

That cast is ill-formed, and since std::static_pointer_cast that you are attempting will perform this same cast, that is ill-formed as well.

what do I need to do to make it work?

You can do the same static_cast that works in your non-pointer example:

int i = static_cast<int>(*pa); //works i = 42

However, if you want to have a pointer that shares ownership with pa and produces an int prvalue on indirection, then I don't think this is achievable with standard smart pointers directly. You could define a custom one. Something along these lines:

template<class C, class P>
class converting_ptr {
public:
    converting_ptr(P ptr): ptr(std::move(ptr)) {}

    C operator*() {
        return static_cast<C>(*ptr);
    }

private:
    P ptr;
};

template<class C, class P>
converting_ptr<C, P>
make_converting_ptr(P ptr)
{
    return {std::move(ptr)};
}


int main()
{
    std::shared_ptr<A> pa = std::make_shared<A>();
    auto pi = make_converting_ptr<int>(pa);
    int i = *pi; //works i = 42
}

P.S. Your static_cast is ill-formed because

  1. A a(); is a function declaration.
  2. A::operator int is private.
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Not any more. C++20 provides a specialization for `std::shared_ptr`. – Sam Varshavchik Apr 22 '22 at 15:18
  • 2
    @SamVarshavchik Are you sure? All I can find is they added support to cast from rvalues. cppreference states even for the new c++20 added overload that `The behavior is undefined unless static_cast((U*)nullptr) is well formed.` – NathanOliver Apr 22 '22 at 15:27
  • This is what I'm looking at: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast – Sam Varshavchik Apr 22 '22 at 15:42
  • 3
    @SamVarshavchik And on that page it has `The behavior of these functions is undefined unless the corresponding cast from U* to T* is well formed:` The is no well formed static cast from `A*` to `int*`. – NathanOliver Apr 22 '22 at 15:43
  • I double checked that my comment's timestamp was within 5 minutes of the original answer, and I my recollection was that the initial version of this answer was referencing `static_cast`; furthermore my comment was merely the fact that an appropriate specialization of `static_pointer_cast` for `std::shared_ptr` was added in C++20. – Sam Varshavchik Apr 22 '22 at 16:33
  • 1
    @SamVarshavchik `static_pointer_cast` has been around since C++11. C++20 only added a rvalue reference overload which does the same thing as the lvalue once, but also guaranteed that the passed in pointer is nulled out. – NathanOliver Apr 22 '22 at 16:38
  • @eerorika "*if you want to have a pointer that shares ownership with `pa` and produces an `int` prvalue on indirection, then I don't think this is achievable with standard smart pointers directly*" - what about when using `shared_ptr`'s aliasing constructor? – Remy Lebeau Apr 22 '22 at 16:41
  • @RemyLebeau I don't think you can make that work with a prvalue. It's useful when you have an `int` object whose lifetime is bound to the `A` object. But you don't have such connection between the lifetimes here. – eerorika Apr 22 '22 at 16:43