Search code examples
c++shared-ptr

'<function-style-cast>': cannot convert from 'initializer list' to 'std::shared_ptr<SDL_Window>' on creating shared_ptr with custom deleter


I created the wrapper functors for SDL2 library methods to return smart pointers with custom deleters. It seems to work fine for unqiue_ptr (class Image) but gives following error for classes returning shared_ptr (class Window) during build:

'<function-style-cast>': cannot convert from 'initializer list' to 'std::shared_ptr<SDL_Window>'

The SDL_CreateWindow here returns raw SDL_Window* and the IMG_Load returns raw SDL_Surface*.

I have tried moving Deleter to public and removing copy restrictions of Window class, but still it fails with same error. Also, if I just return the nullptr from Window's function cast, it builds fine then. So the issue seems to be with the creation of the shared_ptr itself. What boggles me is why it works fine with unique_ptr but not shared_ptr.

#pragma once
#include <memory>
#include <SDL.h>
#include "Uncopyable.h"

// fails during build with error: '<function-style-cast>': 
// cannot convert from 'initializer list' to 'std::shared_ptr<SDL_Window>'
class Window:private Uncopyable {
private:


public:
    class Deleter {
        void operator()(SDL_Window *window) {
            SDL_DestroyWindow(window);
        }
    };

    static const int SCREEN_WIDTH = 800;
    static const int SCREEN_HEIGHT = 600;
    std::shared_ptr<SDL_Window> operator()() const {
        return std::shared_ptr<SDL_Window>(
            SDL_CreateWindow("SDL Tutorial",
                SDL_WINDOWPOS_UNDEFINED,
                SDL_WINDOWPOS_UNDEFINED,
                SCREEN_WIDTH,
                SCREEN_HEIGHT,
                SDL_WINDOW_SHOWN),
            Deleter());
    }
};

#pragma once
#include <memory>
#include <string>
#include <SDL.h>
#include <SDL_image.h>
#include "Uncopyable.h"

// builds fine
class Image: private Uncopyable {
public:
    class Deleter{
        void operator()(SDL_Surface *image) {
            SDL_FreeSurface(image);
        }
    };

    std::unique_ptr<SDL_Surface, Deleter> operator()(const std::string &path) const {
        return std::unique_ptr<SDL_Surface, Deleter>(
            IMG_Load(path.c_str()),
            Deleter());
    }
};

Expected result: Window class should build without error like Image class

Actual result: Window class fails with error given above whereas Image class builds fine

Update: Narrowing down further by moving the the shared_ptr creation logic to simple function, I found that removing the custom Deleter() removes build error. So it seems to be the culprit. But I need the Deleter and also, why same contruct works fine with Image using unique_ptr.


Solution

  • I have slimmed your example down a little:

    #include <memory>
    
    // Stub these out since we don't have them available and they don't really matter
    // for the substance of the question.
    struct SDL_Window {};
    void SDL_DestroyWindow( SDL_Window* win ) { delete win; }
    SDL_Window* SDL_CreateWindow() { return new SDL_Window{}; }
    
    // fails during build with error: '<function-style-cast>': 
    // cannot convert from 'initializer list' to 'std::shared_ptr<SDL_Window>'
    class Window {
    public:
        class Deleter {
            void operator()(SDL_Window *window) {
                SDL_DestroyWindow(window);
            }
        };
    
        std::shared_ptr<SDL_Window> operator()() const {
            return std::shared_ptr<SDL_Window>(
                SDL_CreateWindow(),
                Deleter());
        }
    };
    
    int main()
    {
        auto win = Window();
        auto sp = win();
    }
    

    Now the problem is more apparent:

    /usr/local/include/c++/8.2.0/bits/shared_ptr_base.h:642:11: error: 
      'void Window::Deleter::operator()(SDL_Window*)' is private within this context
            __d(__p); // Call _Deleter on __p.
            ~~~^~~~~
    
    main.cpp:16:14: note: declared private here
             void operator()(SDL_Window *window) {
    

    See it fail live on Coliru.

    If you add public to your deleter class or make it a struct, it will work. But, you could also skip that class and just pass in the deleting function directly if that's all the work it needs to do (or use a lambda if it's a bit more complicated):

        std::shared_ptr<SDL_Window> operator()() const {
            return std::shared_ptr<SDL_Window>(
                SDL_CreateWindow(),
                SDL_DestroyWindow);
        }
    
        // Or with a lambda if it's more complicated (here also using a factory func)
        static std::shared_ptr<SDL_Window> Create() {
            return std::shared_ptr<SDL_Window>(
                SDL_CreateWindow(),
                [] (auto win) { 
                    UnregisterMyWindow( win );
                    SDL_DestroyWindow( win ); 
                 });
        }
    

    See it work live on Coliru.

    Also, it is of questionable wisdom to use Window::operator() like that. I'd suggest you make a non-member or static member factory function instead to make windows.