non-heap std::function. велосипед?
От: sokel Россия  
Дата: 08.08.14 13:53
Оценка: 125 (4)
Дорвался вот до С++ 11 и придумалась такая штука... Есть некий асинхронный интерфейс, обрабатывающий запросы. С запросом надо связать callback. std::function отлично подходит, но встал вопрос оптимизации в плане выделения памяти. Конечно в студии, например, есть оптимизация хранения небольших функторов (хотя та ещё оптимизация, если рассчитывать на быстрый move). Или на худой конец custom аллокатор можно подсунуть (только вот gcc custom аллокаторы не умеет). Ну а callback'и то копеечные, thiscall какой нибудь плюс пара-тройка связанных ref/smartptr аргументов или та же лямбда с захватом this. Собственно почему бы не сляпать свой non-copyable/non-movable вариант со статическим хранилищем заданного размера? Размер можно примерно подобрать, исходя из используемых callback-функторов.

template<typename Ret, typename ...Args>
class stack_function_base {
public:
    virtual Ret do_call(Args&& ...) = 0;
    virtual ~stack_function_base() {}
};

template<typename F, typename Ret, typename ...Args>
class stack_function_impl : public stack_function_base<Ret, Args...>
{
public:
    stack_function_impl(F&& fn)
        : fn_(std::forward<F>(fn))
    {}
    Ret do_call(Args&& ... args) override final {
        return fn_(std::forward<Args>(args)...);
    }
private:
    F fn_;
};

template<size_t MaxSize, typename Ret, typename ...Args>
class stack_function
{
    typedef stack_function_base<Ret, Args...> fn_interface;
public:
    stack_function() : fn_(nullptr) {}
    stack_function(const stack_function&) = delete;
    stack_function& operator =(const stack_function&) = delete;
    template<typename F>
    stack_function(F&& f) : fn_(0) {
        construct_(std::forward<F>(f));
    }
    template<typename F>
    void assign(F&& f) {
        if(fn_) destroy_();
        construct_(std::forward<F>(f));
    }
    ~stack_function() {
        if(fn_) destroy_();
    }
    operator bool() const {
        return fn_ != nullptr;
    }
    Ret operator()(Args&& ... args) {
        if(!fn_)
            throw std::bad_function_call();
        return fn_->do_call(std::forward<Args>(args)...);
    }
private:
    void destroy_() {
        fn_->~fn_interface();
        fn_ = 0;
    }
    template<typename F>
    void construct_(F&& f) {
        typedef stack_function_impl<F, Ret, Args...> fn_type;
        static_assert(sizeof(fn_type) <= MaxSize
                      , "non sufficient stack_function storage size");
        fn_ = new (fn_storage_.data_) fn_type(std::forward<F>(f));
    }
private:
    fn_interface* fn_;
    union {
        long double align_;
        unsigned char data_[MaxSize];
    } fn_storage_;
};


Вроде работает, может кому пригодится.
typedef <72, void, /* callback arguments */> request_callback;
...
template<typename F>
void request::set_callback(F&& f) {
    callback_.assign(std::forward<F>(f));
}
Re: non-heap std::function. велосипед?
От: inkooboo  
Дата: 09.08.14 15:20
Оценка:
Здравствуйте, sokel, Вы писали:

S>Дорвался вот до С++ 11 и придумалась такая штука...


Я такое тоже писал, при этом практически повторил синтаксис std::function =)
https://github.com/inkooboo/thread-pool-cpp11/blob/master/thread_pool/fixed_function.hpp

Пример использования:
https://github.com/inkooboo/snippets-of-my-dreams/blob/master/test/fixed_function.cpp
Re[2]: non-heap std::function. велосипед?
От: _NN_ www.nemerleweb.com
Дата: 10.08.14 06:08
Оценка:
Здравствуйте, inkooboo, Вы писали:

I>Здравствуйте, sokel, Вы писали:


S>>Дорвался вот до С++ 11 и придумалась такая штука...


I>Я такое тоже писал, при этом практически повторил синтаксис std::function =)

I>https://github.com/inkooboo/thread-pool-cpp11/blob/master/thread_pool/fixed_function.hpp

I>Пример использования:

I>https://github.com/inkooboo/snippets-of-my-dreams/blob/master/test/fixed_function.cpp

Вы лучше это дело в буст продвиньте , а там глядишь и в стандарт войдет.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 10.08.14 20:47
Оценка: +1
Здравствуйте, inkooboo, Вы писали:

I>Я такое тоже писал, при этом практически повторил синтаксис std::function =)

I>https://github.com/inkooboo/thread-pool-cpp11/blob/master/thread_pool/fixed_function.hpp

void moveFromOther(FixedFunction &o) {
    ...
    memcpy(&m_storage, &o.m_storage, STORAGE_SIZE);


Это не move, а разложенные грабли. Если нужен move, то либо интерфейс функтора с явным виртуальным move, либо ещё одна статическая обертка (m_move_ptr).
Re[3]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 11.08.14 07:53
Оценка:
Здравствуйте, sokel, Вы писали:

S>Если нужен move, то либо интерфейс функтора с явным виртуальным move


Можно, кстати, сделать возможность move/copy параметром шаблона.
Re[4]: non-heap std::function. велосипед?
От: uzhas Ниоткуда  
Дата: 11.08.14 08:10
Оценка: 12 (1)
Здравствуйте, sokel, Вы писали:

S>Можно, кстати, сделать возможность move/copy параметром шаблона.


я бы посоветовал заменить long double на обычный double или на int64_t, чтобы гарантировать максимальное выравнивание (если я правильно понял назначение union)
http://ideone.com/xolMTT
Re[5]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 11.08.14 08:19
Оценка:
Здравствуйте, uzhas, Вы писали:

U>я бы посоветовал заменить long double на обычный double или на int64_t, чтобы гарантировать максимальное выравнивание (если я правильно понял назначение union)

U>http://ideone.com/xolMTT

Ага, спасибо, а ещё лучше наверное на std::aligned_storage<MaxSize>.
Re[6]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 11.08.14 09:21
Оценка:
Здравствуйте, sokel, Вы писали:

S>Ага, спасибо, а ещё лучше наверное на std::aligned_storage<MaxSize>.


aligned_storage вместо union + move/copy + параметризация сигнатурой (typedef stack_function<void(), 8>):
#include <functional>

#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif

namespace details {
    template<typename Ret, typename ...Args>
    class stack_function_interface {
        typedef stack_function_interface<Ret, Args...> self_type;
    public:
        virtual Ret do_call(Args&& ...) = 0;
        virtual ~stack_function_interface() {}
        virtual self_type* copy_to(void*) const = 0;
        virtual self_type* move_to(void*) = 0;
    };

    template<typename F, typename Ret, typename ...Args>
    class stack_function_impl : public stack_function_interface<Ret, Args...>
    {
        typedef stack_function_interface<Ret, Args...> base;
    public:
        stack_function_impl(F&& fn)
            : fn_(std::forward<F>(fn))
        {}
        stack_function_impl(const stack_function_impl& rhs)
            : fn_(rhs.fn_)
        {}
        stack_function_impl(stack_function_impl& rhs)
            : fn_(rhs.fn_)
        {}
        stack_function_impl(stack_function_impl&& rhs)
            : fn_(std::forward<F>(rhs.fn_))
        {}
        Ret do_call(Args&& ... args) override final {
            return fn_(std::forward<Args>(args)...);
        }
        base* copy_to(void* addr) const override final {
            return new (addr)stack_function_impl(*this);
        }
        base* move_to(void* addr) override final {
            return new (addr)stack_function_impl(std::move(*this));
        }
    private:
        F fn_;
    };

    template<size_t MaxSize, typename Ret, typename ...Args>
    class stack_function_base
    {
    public:
        Ret operator()(Args&& ... args) {
            if(!fn_)
                throw std::bad_function_call();
            return fn_->do_call(std::forward<Args>(args)...);
        }
    protected:
        typedef stack_function_interface<Ret, Args...> fn_interface;
        stack_function_base() : fn_(nullptr)
        {}
        template<typename F>
        void assign_(F&& f) {
            destroy_();
            construct_(std::forward<F>(f));
        }
        ~stack_function_base() { destroy_(); }
        bool empty_() const {
            return fn_ == nullptr;
        }
        void destroy_() {
            if(fn_) {
                fn_->~fn_interface();
                fn_ = 0;
            }
        }
        template<typename F>
        void construct_(F&& f) {
            typedef stack_function_impl<F, Ret, Args...> fn_type;
            static_assert(sizeof(fn_type) <= MaxSize
                          , "non sufficient stack_function storage size");
            fn_ = new (&fn_storage_) fn_type(std::forward<F>(f));
        }
        void move_construct_(stack_function_base&& rhs) {
            if(rhs.fn_)
                fn_ = rhs.fn_->move_to(&fn_storage_);
        }
        void copy_construct_(const stack_function_base& rhs) {
            if(rhs.fn_)
                fn_ = rhs.fn_->copy_to(&fn_storage_);
        }

    private:
        fn_interface* fn_;
        using fn_storage_t = typename std::aligned_storage<MaxSize>::type;
        fn_storage_t fn_storage_;
    };

    template<class Function, size_t MaxSize> struct get_stack_function_base;

    template<size_t MaxSize, typename Ret, typename ...Args>
    struct get_stack_function_base<Ret(Args...), MaxSize> {
        typedef stack_function_base<MaxSize, Ret, Args...> type;
    };
}

template<typename Function, size_t MaxSize>
class stack_function : public details::get_stack_function_base<Function, MaxSize>::type
{
    typedef typename details::get_stack_function_base<Function, MaxSize>::type base;
public:
    template<typename F, size_t S> stack_function(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&&) = delete;
    template<typename F, size_t S> void assign(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&&) = delete;

    stack_function() {}
    stack_function(const stack_function& rhs) { base::copy_construct_(rhs); }
    stack_function(stack_function& rhs) { base::copy_construct_(rhs); }
    stack_function(stack_function&& rhs) { base::move_construct_(std::forward<base>(rhs)); }
    stack_function& operator =(const stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
    stack_function& operator =(stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
    stack_function& operator=(stack_function&& rhs) { base::destroy_(); base::move_construct_(std::forward<base>(rhs)); return *this; }
    template<typename F> stack_function(F&& f) { base::construct_(std::forward<F>(f)); }
    template<typename F> void assign(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); }
    template<typename F> stack_function& operator=(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); return *this; }
    operator bool() const { return !base::empty_(); }
};
#ifdef _WIN32
#pragma warning( pop )
#endif
Re[7]: non-heap std::function. велосипед?
От: Nuzhny Россия https://github.com/Nuzhny007
Дата: 11.08.14 09:52
Оценка:
Здравствуйте, sokel, Вы писали:

[off]
Вот, блин! Читаю твой пост и понимаю, что несмотря на непрерывный десятилетний стаж С++ надо учить заново. Не ждать же, когда контора на новый стандарт переползёт.
[/off]
Re[7]: non-heap std::function. велосипед?
От: uzhas Ниоткуда  
Дата: 11.08.14 13:10
Оценка: 12 (1)
Здравствуйте, sokel, Вы писали:


S>aligned_storage вместо union + move/copy + параметризация сигнатурой (typedef stack_function<void(), 8>):

обложил нотами, может быть полезными, а могут быть и бесполезными
на абсолютную правоту не претендую
под катом, ибо много букв
  Скрытый текст
#include <functional>

#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif

namespace details {
    //note 0: для пущего перформанса можно было бы упразднить виртуальные вызовы. может серьезно ухужшить читабельность кода, но будет лучше с точки зрения производительности
    //практически это было бы равносильно хранению виртуальной таблицы рядом с объектом, локализация кода улучшилась бы, ликвидировали бы один уровень индиректа имхо
    //в любом случае подобный подход плохо инлайнится, к сожалению. обсуждали здесь : http://rsdn.ru/forum/cpp/5658888.flat
    template<typename Ret, typename ...Args>
    class stack_function_interface {
        typedef stack_function_interface<Ret, Args...> self_type;
    public:
        virtual Ret do_call(Args&& ...) = 0;
        virtual ~stack_function_interface() {}
        virtual self_type* copy_to(void*) const = 0;
        virtual self_type* move_to(void*) = 0;
    };

    template<typename F, typename Ret, typename ...Args>
    class stack_function_impl : public stack_function_interface<Ret, Args...>
    {
        typedef stack_function_interface<Ret, Args...> base;
    public:
        //note 1: здесь не универсальная ссылка, поэтому
        //     a) нужен конструктор из (F fn) или из (const F& fn)
        //     b) нужен std::move вместо std::forward
        stack_function_impl(F&& fn)
            : fn_(std::forward<F>(fn))
        {}
        stack_function_impl(const stack_function_impl& rhs)
            : fn_(rhs.fn_)
        {}
        // note 2: ценность данных конструкторов (из неконстантных объектов) для всех классов из этого файла под вопросом (я бы попробовал их удалить)
        stack_function_impl(stack_function_impl& rhs)
            : fn_(rhs.fn_)
        {}
        //note 3: нужен std::move вместо std::forward
        stack_function_impl(stack_function_impl&& rhs)
            : fn_(std::forward<F>(rhs.fn_))
        {}
        Ret do_call(Args&& ... args) override final {
            return fn_(std::forward<Args>(args)...);
        }
        base* copy_to(void* addr) const override final {
            return new (addr)stack_function_impl(*this);
        }
        base* move_to(void* addr) override final {
            return new (addr)stack_function_impl(std::move(*this));
        }
    private:
        F fn_;
    };

    template<size_t MaxSize, typename Ret, typename ...Args>
    class stack_function_base
    {
    public:
        Ret operator()(Args&& ... args) {
            if(!fn_)
                throw std::bad_function_call();
            return fn_->do_call(std::forward<Args>(args)...);
        }
    protected:
        typedef stack_function_interface<Ret, Args...> fn_interface;
        stack_function_base() : fn_(nullptr)
        {}
        template<typename F>
        void assign_(F&& f) {
            destroy_();
            construct_(std::forward<F>(f));
        }
        ~stack_function_base() { destroy_(); }
        bool empty_() const {
            return fn_ == nullptr;
        }
        void destroy_() {
            if(fn_) {
                fn_->~fn_interface();
                //note 4: для однообразия использовать fn_ = nullptr;
                fn_ = 0;
            }
        }
        template<typename F>
        void construct_(F&& f) {
            typedef stack_function_impl<F, Ret, Args...> fn_type;
            //note 5: неравенство можно сделать строгим
            static_assert(sizeof(fn_type) <= MaxSize
                          , "non sufficient stack_function storage size");
            fn_ = new (&fn_storage_) fn_type(std::forward<F>(f));
        }
        void move_construct_(stack_function_base&& rhs) {
            if(rhs.fn_)
                fn_ = rhs.fn_->move_to(&fn_storage_);
           //note 6: я бы сделал rhs.destroy_() чтобы сбросить состояние у объекта, у которого мы все забрали. чаще всего так делают на практике имхо
        }
        void copy_construct_(const stack_function_base& rhs) {
            if(rhs.fn_)
                fn_ = rhs.fn_->copy_to(&fn_storage_);
        }

    private:
        fn_interface* fn_;
        using fn_storage_t = typename std::aligned_storage<MaxSize>::type;
        fn_storage_t fn_storage_;
    };

    template<class Function, size_t MaxSize> struct get_stack_function_base;

    template<size_t MaxSize, typename Ret, typename ...Args>
    struct get_stack_function_base<Ret(Args...), MaxSize> {
        typedef stack_function_base<MaxSize, Ret, Args...> type;
    };
}

//note 7: не следует ли добавить typename после public ?
template<typename Function, size_t MaxSize>
class stack_function : public details::get_stack_function_base<Function, MaxSize>::type
{
    typedef typename details::get_stack_function_base<Function, MaxSize>::type base;
public:
    //note 8: предположу, что достаточно оставить лишь const параметры
    template<typename F, size_t S> stack_function(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&&) = delete;
    template<typename F, size_t S> void assign(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&&) = delete;

    stack_function() {}
    stack_function(const stack_function& rhs) { base::copy_construct_(rhs); }
    //note 9: ценность конструктора под вопросом (писал об этом выше)
    stack_function(stack_function& rhs) { base::copy_construct_(rhs); }
    //note 10: нужен std::move(rhs) вместо std::forward<base>
    stack_function(stack_function&& rhs) { base::move_construct_(std::forward<base>(rhs)); }
    stack_function& operator =(const stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
    stack_function& operator =(stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
    //note 11: нужен std::move(rhs) вместо std::forward<base>
    stack_function& operator=(stack_function&& rhs) { base::destroy_(); base::move_construct_(std::forward<base>(rhs)); return *this; }
    template<typename F> stack_function(F&& f) { base::construct_(std::forward<F>(f)); }
    template<typename F> void assign(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); }
    template<typename F> stack_function& operator=(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); return *this; }
    //note 12: safe bool idiom более безопасно было бы применить
    operator bool() const { return !base::empty_(); }
};
#ifdef _WIN32
#pragma warning( pop )
#endif
Re[8]: non-heap std::function. велосипед?
От: wander  
Дата: 11.08.14 14:21
Оценка: 12 (1)
Здравствуйте, uzhas, Вы писали:

U>//note 7: не следует ли добавить typename после public ?

U>template<typename Function, size_t MaxSize>
U>class stack_function : public details::get_stack_function_base<Function, MaxSize>::type

Не надо. В стандарте это отдельно оговорено, что при наследовании из контекста понятно, что ничего кроме типа не может быть. А значит typename не нужен.
Re[8]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 11.08.14 15:30
Оценка: 18 (1)
Здравствуйте, uzhas, Вы писали:

U>обложил нотами, может быть полезными, а могут быть и бесполезными

U>на абсолютную правоту не претендую
U>под катом, ибо много букв
Спасибо.

U>//note 8: предположу, что достаточно оставить лишь const параметры

Неконстантные конструкторы копирования нужны чтобы отвадить компилятор от шаблонного конструктора.
Если на входе неконстантная ссылка, ADL считает конструктор <F>(F&&) предпочтительным, т.к. он best viable function, не требует конверсии noon-const->const.

U>//note 6: я бы сделал rhs.destroy_() чтобы сбросить состояние у объекта, у которого мы все забрали. чаще всего так делают на практике имхо

Ну практики у меня нет, считал что по барабану, стандарт допускает, оно ж в валидном состоянии остается. Лишние действия, которые и так в деструкторе выполнятся.

Переписал без виртуальных вызовов. В принципе move/copy можно выкинуть если не нужны.

  Скрытый текст
#include <functional>

#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif
namespace details {
    template<size_t MaxSize, typename Ret, typename ...Args>
    class stack_function_base
    {
        typedef void(*destruct_fn)(void*);
        typedef void(*call_fn)(void*, Args...);
        typedef void(*move_fn)(void*, void*);
        typedef void(*copy_fn)(const void*, void*);
        template<typename F> static void destruct_impl_(void* obj) {
            static_cast<F*>(obj)->~F();
        }
        template<typename F> static void move_impl_(void* from, void* to) {
            new(to) F(std::move(*static_cast<F*>(from)));
        }
        template<typename F> static void copy_impl_(const void* from, void* to) {
            new(to) F(*static_cast<const F*>(from));
        }
        template<typename F> static Ret call_impl_(void* obj, Args&& ... args) {
            return static_cast<F*>(obj)->operator()(std::forward<Args>(args)...);
        }
    public:
        Ret operator()(Args&& ... args) {
            if(!fn_)
                throw std::bad_function_call();
            return fn_(&fn_storage_, std::forward<Args>(args)...);
        }
    protected:
        stack_function_base() {}
        ~stack_function_base() { destroy_(); }
        bool empty_() const {
            return fn_ == nullptr;
        }
        void destroy_() {
            if(fn_) {
                destruct_(&fn_storage_);
                fn_ = nullptr;
                destruct_ = nullptr;
                move_ = nullptr;
                copy_ = nullptr;
            }
        }
        template<typename F>
        void construct_(F&& f) {
            static_assert(sizeof(F) <= MaxSize
                          , "non sufficient stack_function storage size");
            new (&fn_storage_) F(std::forward<F>(f));
            fn_ = &call_impl_<F, Args...>;
            destruct_ = &destruct_impl_<F>;
            move_ = &move_impl_<F>;
            copy_ = &copy_impl_<F>;
        }
        void move_construct_(stack_function_base&& rhs) {
            if(rhs.fn_) {
                rhs.move_(&rhs.fn_storage_, &fn_storage_);
                fn_ = rhs.fn_;
                destruct_ = rhs.destruct_;
                move_ = rhs.move_;
                copy_ = rhs.copy_;
                rhs.destroy_();
            }
        }
        void copy_construct_(const stack_function_base& rhs) {
            if(rhs.fn_) {
                rhs.copy_(&rhs.fn_storage_, &fn_storage_);
                fn_ = rhs.fn_;
                destruct_ = rhs.destruct_;
                move_ = rhs.move_;
                copy_ = rhs.copy_;
            }
        }
    private:
        call_fn fn_ = nullptr;
        destruct_fn destruct_ = nullptr;
        move_fn move_ = nullptr;
        copy_fn copy_ = nullptr;
        using fn_storage_t = typename std::aligned_storage<MaxSize>::type;
        fn_storage_t fn_storage_;
    };

    template<class Function, size_t MaxSize> struct get_stack_function_base;

    template<size_t MaxSize, typename Ret, typename ...Args>
    struct get_stack_function_base<Ret(Args...), MaxSize> {
        typedef stack_function_base<MaxSize, Ret, Args...> type;
    };
}

template<typename Function, size_t MaxSize>
class stack_function : public details::get_stack_function_base<Function, MaxSize>::type
{
    typedef typename details::get_stack_function_base<Function, MaxSize>::type base;
public:
    template<typename F, size_t S> stack_function(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function(stack_function<F, S>&&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(stack_function<F, S>&) = delete;
    template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&&) = delete;
    template<typename F, size_t S> void assign(const stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&) = delete;
    template<typename F, size_t S> void assign(stack_function<F, S>&&) = delete;

    stack_function() {}
    stack_function(const stack_function& rhs) {
        base::copy_construct_(rhs);
    }
    stack_function(stack_function& rhs) {
        base::copy_construct_(rhs);
    }
    stack_function(stack_function&& rhs) {
        base::move_construct_(std::forward<base>(rhs));
    }
    stack_function& operator=(const stack_function& rhs) {
        base::destroy_();
        base::copy_construct_(rhs);
        return *this;
    }
    stack_function& operator=(stack_function& rhs) {
        base::destroy_();
        base::copy_construct_(rhs);
        return *this;
    }
    stack_function& operator=(stack_function&& rhs) {
        base::destroy_();
        base::move_construct_(std::forward<base>(rhs));
        return *this;
    }
    template<typename F> stack_function(F&& f) {
        base::construct_(std::forward<F>(f));
    }
    template<typename F> void assign(F&& f) {
        base::destroy_();
        base::construct_(std::forward<F>(f));
    }
    template<typename F> stack_function& operator=(F&& f) {
        base::destroy_();
        base::construct_(std::forward<F>(f));
        return *this;
    }
    operator bool() const {
        return !base::empty_();
    }
    void reset() {
        base::destroy_();
    }
};
#ifdef _WIN32
#pragma warning( pop )
#endif


#include <iostream>

struct foo {
    int i;
    foo(int i) : i(i) { std::cout << "foo constructor" << std::endl; }
    foo(const foo& r) : i(r.i) { std::cout << "foo copy constructor" << std::endl; }
    foo(foo&& r) : i(r.i) { std::cout << "foo move constructor" << std::endl; }
    void operator()() { std::cout << "foo call (" << i << ")" << std::endl; }
};

int main()
{
    typedef stack_function<void(), 64> callback;
    callback c1 = foo(42);
    c1();
    callback c2 = std::move(c1);
    c2();
    callback c3 = c2;
    c3();
    return 0;
}
Re[9]: non-heap std::function. велосипед?
От: PM  
Дата: 12.08.14 03:35
Оценка: 18 (1)
Здравствуйте, sokel, Вы писали:

Хм, поздно я увидел эту тему Примерно с год назад делал похожую реализацию, изначально с ипользованием указателей на функции. В качестве идеи послужил такой код из реализации Disruptor

struct functor
{
    functor():callback(nullptr),destruct(nullptr){}
    void call(){ callback( _buffer ); }

    void (*callback)( void* self ); 
    void (*destruct)( void* self );
    char _buffer[256];
};

template<typename Functor>
struct functor_invoker
{
    static void run( void* self )
    {
        (*((Functor*)self))();
    }
    static void destruct( void* self )
    {
        ((Functor*)self)->~Functor();
    }
};


Но в тогда в проекте было ограничение на Visual C++ 2010. Можно, конечно, было пустить в ход Boost.Preprocessor и нагенерировать кучу специализаций шаблонов для разного количества аргументов, но в итогое хватило поддержки только сигнатуры void()

Откопал тот код, перевел его в С++11, результаты поместил в https://github.com/pmed/fixed_size_function

Полная реализация пот катом. Из бонусов — доступна информация о сигнатуре функции, более близкий к std::function интерфейс. Проверял только с Visual C++ 2013 Update 3 и GCC 4.9.0 на http://coliru.stacked-crooked.com/

  Скрытый текст
#include <stdexcept>
#include <functional>
#include <tuple>
#include <type_traits>

template<typename Signature, size_t MaxSize>
class fixed_size_function;

template<typename Ret, typename ...Args, size_t MaxSize>
class fixed_size_function<Ret (Args...), MaxSize>
{
public:
    using result_type = Ret;

    static const std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "invalid argument index");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };

    fixed_size_function()
        : call_(nullptr)
        , destroy_(nullptr)
        , copy_(nullptr)
        , move_(nullptr)
    {
    }

    fixed_size_function(std::nullptr_t)
        : fixed_size_function()
    {
    }

    ~fixed_size_function()
    {
        reset();
    }

    fixed_size_function& operator=(std::nullptr_t)
    {
        reset();
        return *this;
    }

    fixed_size_function(fixed_size_function const& src)
    {
        copy(src);
    }

    fixed_size_function& operator=(fixed_size_function const& src)
    {
        assign(src);
        return *this;
    }

    fixed_size_function(fixed_size_function& src)
    {
        copy(src);
    }
    
    fixed_size_function& operator=(fixed_size_function& src)
    {
        assign(src);
        return *this;
    }

    fixed_size_function(fixed_size_function&& src)
    {
        move(std::forward<fixed_size_function>(src));
    }

    fixed_size_function& operator=(fixed_size_function&& src)
    {
        assign(std::forward<fixed_size_function>(src));
        return *this;
    }

    template<typename Functor>
    fixed_size_function(Functor&& f)
    {
        create(std::forward<Functor>(f));
    }

    template<typename Functor>
    fixed_size_function& operator=(Functor&& f)
    {
        assign(std::forward<Functor>(f));
        return *this;
    }

    void assign(fixed_size_function const& src)
    {
        reset();
        copy(src);
    }

    void assign(fixed_size_function& src)
    {
        reset();
        copy(src);
    }

    void assign(fixed_size_function&& src)
    {
        reset();
        move(std::forward<fixed_size_function>(src));
    }

    template<typename Functor>
    void assign(Functor&& f)
    {
        reset();
        create(std::forward<Functor>(f));
    }

    void reset()
    {
        if (destroy_)
        {
            destroy_(&storage_);
            call_ = nullptr;
            copy_ = nullptr;
            move_ = nullptr;
            destroy_ = nullptr;
        }
    }

    explicit operator bool() const { return call_ != nullptr; }

    Ret operator()(Args&& ... args)
    {
        return call_ ? call_(&storage_, std::forward<Args>(args)...) : throw std::bad_function_call();
    }

    void swap(fixed_size_function& other)
    {
        using std::swap;

        swap(call_, other.call_);
        swap(copy_, other.copy_);
        swap(move_, other.move_);
        swap(destroy_, other.destroy_);
        swap(storage_, other.storage_);
    }

    friend void swap(fixed_size_function& lhs, fixed_size_function& rhs)
    {
        lhs.swap(rhs);
    }

    friend bool operator==(std::nullptr_t, fixed_size_function const& f)
    {
        return !f;
    }

    friend bool operator==(fixed_size_function const& f, std::nullptr_t)
    {
        return !f;
    }

    friend bool operator!=(std::nullptr_t, fixed_size_function const& f)
    {
        return f;
    }

    friend bool operator!=(fixed_size_function const& f, std::nullptr_t)
    {
        return f;
    }

private:
    template<typename Functor>
    void create(Functor&& f)
    {
        typedef typename std::decay<Functor>::type functor_type;
        static_assert(sizeof(functor_type) <= MaxSize, "Functor must be smaller than storage buffer");

        new (&storage_) functor_type(std::forward<Functor>(f));

        call_ = &call_impl<functor_type>;
        destroy_ = &destroy_impl<functor_type>;
        copy_ = &copy_impl<functor_type>;
        move_ = &move_impl<functor_type>;
    }

    void copy(fixed_size_function const& src)
    {
        if (src.copy_)
        {
            src.copy_(&src.storage_, &storage_);

            call_ = src.call_;
            copy_ = src.copy_;
            move_ = src.move_;
            destroy_ = src.destroy_;
        }
    }

    void move(fixed_size_function&& src)
    {
        if (src.move_)
        {
            src.move_(&src.storage_, &storage_);

            call_ = src.call_; src.call_ = nullptr;
            copy_ = src.copy_; src.copy_ = nullptr;
            move_ = src.move_; src.move_ = nullptr;
            destroy_ = src.destroy_; src.destroy_ = nullptr;
        }
    }

private:
    template<typename Functor>
    static Ret call_impl(void* functor, Args&& ... args)
    {
        return (*static_cast<Functor*>(functor))(std::forward<Args>(args)...);
    }

    template<typename Functor>
    static void destroy_impl(void* functor)
    {
        static_cast<Functor*>(functor)->~Functor();
    }

    template<typename Functor>
    static void copy_impl(void const* functor, void* dest)
    {
        new (dest) Functor(*static_cast<Functor const*>(functor));
    }

    template<typename Functor>
    static void move_impl(void* functor, void* dest)
    {
        new (dest) Functor(std::move(*static_cast<Functor*>(functor)));
    }

    Ret  (*call_)(void* functor, Args&& ... args);
    void (*destroy_)(void* functor);
    void (*copy_)(void const* functor, void* dest);
    void (*move_)(void* functor, void* dest);

    typename std::aligned_storage<MaxSize>::type storage_;
};


В процессе разработки для меня осталось не понятным, почему при присваивании используется в качестве оператора присваивания используется перегрузка для неконстантной ссылки:

int f(int x) { return x; }

fixed_size_function<int(int), 256> fun(x), fun2;

fun2 = fun; // здесь Visual C++ 2013 использует fixed_size_function::operator=(fixed_size_function& src)
Re[10]: non-heap std::function. велосипед?
От: uzhas Ниоткуда  
Дата: 12.08.14 08:06
Оценка: 11 (2)
Здравствуйте, PM, Вы писали:

у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным

рассмотрим код:
//здесь нет универсальной ссылки, поэтому подправляю код
fixed_size_function(fixed_size_function&& src)
{
  //move(std::forward<fixed_size_function>(src));
  move(std::move(src)); // <-- более точно передает намерение: передвинуть (move) объект. тип нам уже известен
}


а вот здесь std::forward используется правильно, т.к. работаем с универсальной ссылкой
template<typename Functor>
fixed_size_function(Functor&& f)
{
  create(std::forward<Functor>(f)); // <-- пробрасываем (forward) шаблонный тип, выведенный компилятором
}


об этом можно почитать здесь:
http://stackoverflow.com/questions/13219484/can-i-typically-always-use-stdforward-instead-of-stdmove
http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
Re[10]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 09:00
Оценка:
Здравствуйте, PM, Вы писали:

PM>Полная реализация пот катом. Из бонусов — доступна информация о сигнатуре функции, более близкий к std::function интерфейс. Проверял только с Visual C++ 2013 Update 3 и GCC 4.9.0 на http://coliru.stacked-crooked.com/


Супер, объединил сейчас реализации. И название fixed_size_function конечно более удачное.

Добавил ещё один параметр в шаблон, Movable. Просто если overhead от move/copy не нужен хотелось бы ещё и быть уверенным что они не выполняются никогда.
  Скрытый текст
template<typename Function, size_t MaxSize, bool Movable>
class fixed_size_function;
...
template<size_t MaxSize, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, true>
{
...
template<size_t MaxSize, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, false>
{


Из своей сейчас на всякий случай оставил запрет на рекурсию, мало ли:
  Скрытый текст
    template<typename F, size_t S, bool M> fixed_size_function(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> void assign(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&&) = delete;


Немножко exception safety в reset:
  Скрытый текст
    void reset() {
        auto dtor = dtor_;
        if(dtor) {
            reset_vtable_();
            dtor(&storage_);
        }
    }
...
    void reset_vtable_() {
        call_ = nullptr;
        dtor_ = nullptr;
        copy_ = nullptr;
        move_ = nullptr;
    }


В move у тебя похоже ошибка:
  Скрытый текст
    void move(fixed_size_function&& src)
    {
        if (src.move_)
        {
            src.move_(&src.storage_, &storage_);

            call_ = src.call_; src.call_ = nullptr;
            copy_ = src.copy_; src.copy_ = nullptr;
            move_ = src.move_; src.move_ = nullptr;
            // здесь переместили деструктор, он не будет вызван для src
            destroy_ = src.destroy_; src.destroy_ = nullptr;
        }
    }
    Сделал так примерно:
    void copy_vtable_(const fixed_size_function& rhs) {
        call_ = rhs.call_;
        dtor_ = rhs.dtor_;
        copy_ = rhs.copy_;
        move_ = rhs.move_;
    }
    ...
    void move_construct_(fixed_size_function&& rhs) {
        if(!rhs.move_) return;
        rhs.move_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
        rhs.reset();
    }


PM>В процессе разработки для меня осталось не понятным, почему при присваивании используется в качестве оператора присваивания используется перегрузка для неконстантной ссылки:

Ну так опять таки просто потому что версия T& vs const T& версии оператора присваивания есть best viable function, т.к. приведение не нужно.

Ну и полностью вот:
  Скрытый текст
#include <functional>
#include <stdexcept>
#include <functional>
#include <tuple>
#include <type_traits>

#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif

template<typename Function, size_t MaxSize, bool Movable>
class fixed_size_function;

template<size_t MaxSize, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, true>
{
public:
    template<typename F, size_t S, bool M> fixed_size_function(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> void assign(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&&) = delete;

    using result_type = Ret;

    static const std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "invalid argument index");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };

    fixed_size_function() = default;

    fixed_size_function(std::nullptr_t) {
    }
    ~fixed_size_function() {
        reset();
    }
    fixed_size_function(const fixed_size_function& rhs) {
        copy_construct_(rhs);
    }
    fixed_size_function(fixed_size_function& rhs) {
        copy_construct_(rhs);
    }
    fixed_size_function(fixed_size_function&& rhs) {
        move_construct_(std::forward<fixed_size_function>(rhs));
    }
    fixed_size_function& operator=(const fixed_size_function& rhs) {
        assign(rhs);
        return *this;
    }
    fixed_size_function& operator=(fixed_size_function& rhs) {
        assign(rhs);
        return *this;
    }
    fixed_size_function& operator=(fixed_size_function&& rhs) {
        assign(std::forward<fixed_size_function>(rhs));
        return *this;
    }
    void assign(const fixed_size_function& rhs) {
        reset();
        copy_construct_(rhs);
    }
    void assign(fixed_size_function& rhs) {
        reset();
        copy_construct_(rhs);
    }
    void assign(fixed_size_function&& rhs) {
        reset();
        move_construct_(std::forward<fixed_size_function>(rhs));
    }
    template<typename F>
    fixed_size_function(F&& f) {
        construct_(std::forward<F>(f));
    }
    template<typename F>
    fixed_size_function& operator=(F&& f) {
        reset();
        construct_(std::forward<F>(f));
        return *this;
    }
    template<typename F>
    void assign(F&& f) {
        reset();
        construct_(std::forward<F>(f));
    }

    Ret operator()(Args&& ... args) {
        if(!call_)
            throw std::bad_function_call();
        return call_(&storage_, std::forward<Args>(args)...);
    }

    operator bool() const {
        return call_ != nullptr;
    }
    void reset() {
        auto dtor = dtor_;
        if(dtor) {
            reset_vtable_();
            dtor(&storage_);
        }
    }
    void swap(fixed_size_function& rhs) {
        using std::swap;
        swap(call_, rhs.call_);
        swap(dtor_, rhs.dtor_);
        swap(copy_, rhs.copy_);
        swap(move_, rhs.move_);
        swap(storage_, rhs.storage_);
    }

    friend void swap(fixed_size_function& lhs, fixed_size_function& rhs) {
        lhs.swap(rhs);
    }

    friend bool operator==(std::nullptr_t, fixed_size_function const& f) { return !f; }
    friend bool operator==(fixed_size_function const& f, std::nullptr_t) { return !f; }
    friend bool operator!=(std::nullptr_t, fixed_size_function const& f) { return f; }
    friend bool operator!=(fixed_size_function const& f, std::nullptr_t) { return f; }
private:
    template<typename F> static void dtor_impl_(void* obj) {
        static_cast<F*>(obj)->~F();
    }
    template<typename F> static void move_impl_(void* from, void* to) {
        new(to)F(std::move(*static_cast<F*>(from)));
    }
    template<typename F> static void copy_impl_(const void* from, void* to) {
        new(to)F(*static_cast<const F*>(from));
    }
    template<typename F> static Ret call_impl_(void* f, Args&& ... args) {
        return (*static_cast<F*>(f))(std::forward<Args>(args)...);
    }
    void reset_vtable_() {
        call_ = nullptr;
        dtor_ = nullptr;
        copy_ = nullptr;
        move_ = nullptr;
    }
    void copy_vtable_(const fixed_size_function& rhs) {
        call_ = rhs.call_;
        dtor_ = rhs.dtor_;
        copy_ = rhs.copy_;
        move_ = rhs.move_;
    }
    template<typename F>
    void construct_(F&& f) {
        typedef typename std::decay<F>::type functor_type;
        static_assert(sizeof(functor_type) <= MaxSize
                      , "non sufficient fixed_size_function storage size");
        new (&storage_) functor_type(std::forward<F>(f));
        call_ = &call_impl_<functor_type, Args...>;
        dtor_ = &dtor_impl_<functor_type>;
        copy_ = &copy_impl_<functor_type>;
        move_ = &move_impl_<functor_type>;
    }
    void move_construct_(fixed_size_function&& rhs) {
        if(!rhs.move_) return;
        rhs.move_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
        rhs.reset();
    }
    void copy_construct_(const fixed_size_function& rhs) {
        if(!rhs.copy_) return;
        rhs.copy_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
    }
    Ret (*call_)(void*, Args...) = nullptr;
    void(*dtor_)(void*) = nullptr;
    void(*copy_)(const void*, void*) = nullptr;
    void(*move_)(void*, void*) = nullptr;
    typename std::aligned_storage<MaxSize>::type storage_;
};

template<size_t MaxSize, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, false>
{
public:
    template<typename F, size_t S, bool M> fixed_size_function(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function(fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, M>&&) = delete;
    template<typename F, size_t S, bool M> void assign(const fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&) = delete;
    template<typename F, size_t S, bool M> void assign(fixed_size_function<F, S, M>&&) = delete;

    using result_type = Ret;

    static const std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "invalid argument index");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };

    fixed_size_function() = default;

    fixed_size_function(std::nullptr_t) {
    }
    ~fixed_size_function() {
        reset();
    }
    template<typename F>
    fixed_size_function(F&& f) {
        construct_(std::forward<F>(f));
    }
    template<typename F>
    fixed_size_function& operator=(F&& f) {
        reset();
        construct_(std::forward<F>(f));
        return *this;
    }
    template<typename F>
    void assign(F&& f) {
        reset();
        construct_(std::forward<F>(f));
    }
    Ret operator()(Args&& ... args) {
        if(!call_)
            throw std::bad_function_call();
        return call_(&storage_, std::forward<Args>(args)...);
    }
    operator bool() const {
        return call_ != nullptr;
    }
    void reset() {
        auto dtor = dtor_;
        if(dtor) {
            reset_vtable_();
            dtor(&storage_);
        }
    }
    void swap(fixed_size_function& rhs) {
        using std::swap;
        swap(call_, rhs.call_);
        swap(dtor_, rhs.dtor_);
        swap(storage_, rhs.storage_);
    }
    friend void swap(fixed_size_function& lhs, fixed_size_function& rhs) {
        lhs.swap(rhs);
    }
    friend bool operator==(std::nullptr_t, fixed_size_function const& f) { return !f; }
    friend bool operator==(fixed_size_function const& f, std::nullptr_t) { return !f; }
    friend bool operator!=(std::nullptr_t, fixed_size_function const& f) { return f; }
    friend bool operator!=(fixed_size_function const& f, std::nullptr_t) { return f; }
private:
    template<typename F> static void dtor_impl_(void* obj) {
        static_cast<F*>(obj)->~F();
    }
    template<typename F> static Ret call_impl_(void* f, Args&& ... args) {
        return (*static_cast<F*>(f))(std::forward<Args>(args)...);
    }
    void reset_vtable_() {
        call_ = nullptr;
        dtor_ = nullptr;
    }
    template<typename F>
    void construct_(F&& f) {
        typedef typename std::decay<F>::type functor_type;
        static_assert(sizeof(functor_type) <= MaxSize
                      , "non sufficient fixed_size_function storage size");
        new (&storage_) functor_type(std::forward<F>(f));
        call_ = &call_impl_<functor_type, Args...>;
        dtor_ = &dtor_impl_<functor_type>;
    }
    Ret(*call_)(void*, Args...) = nullptr;
    void(*dtor_)(void*) = nullptr;
    typename std::aligned_storage<MaxSize>::type storage_;
};

#ifdef _MSC_VER
#pragma warning( pop )
#endif
Re[11]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 09:38
Оценка: -1
Здравствуйте, uzhas, Вы писали:

U>у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным


Ну как бы может и да, а может и нет... Всё зависит от того как воспринимать forward, мне кажется для forward подходит определение "передать параметр 'as-is'". Что в общем то применимо как к rvalue, так и к универсальным ссылкам.
move же это принудительный вызов rvalue версии. Т.е. forward он как бы forward, конструктор просто передает параметр другой ф-ции, не претендуя на его последующее использование, а вот явный move заставляет обращать внимание — а не воспользуются ли moved объектом после перемещения и т.п.
Re[12]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 09:56
Оценка:
Здравствуйте, sokel, Вы писали:

S>Здравствуйте, uzhas, Вы писали:


U>>у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным


S>Ну как бы может и да, а может и нет... Всё зависит от того как воспринимать forward, мне кажется для forward подходит определение "передать параметр 'as-is'". Что в общем то применимо как к rvalue, так и к универсальным ссылкам.

S>move же это принудительный вызов rvalue версии. Т.е. forward он как бы forward, конструктор просто передает параметр другой ф-ции, не претендуя на его последующее использование, а вот явный move заставляет обращать внимание — а не воспользуются ли moved объектом после перемещения и т.п.

Опять таки move это же не move, а просто каст к rvalue ссылке. Страуструп вон ругался, плохое говорит название.
Да вот например, пишем move конструктор для развесистого типа (ну вот не устраивает нас почему то default):
struct derived : base {
    derived() = default;
    derived(derived&& rhs)
        : base(std::move(rhs))
        , member(std::move(rhs.member))
    {}
    member_type member;
};

При инициализации базы мы "переместили" rhs? А как же тогда мы потом инициализируем member?

А вот forward так не режет глаз:
struct derived : base {
    derived() = default;
    derived(derived&& rhs)
        : base(std::forward<base>(rhs))
        , member(std::forward<member_type>(rhs.member))
    {}
    member_type member;
};


Тем более допустим, что у нас какой то такой фокус с base:
struct base {
    base() = default;
    base(base&&) { std::cout << "base move constructor" << std::endl; }
    template<typename T>
    base(T&&) { std::cout << "base magic constructor" << std::endl; }
};

struct derived_move : base {
    derived_move() = default;
    derived_move(derived_move&& rhs) : base(std::move(rhs)) {}
};

struct derived_forward : base {
    derived_forward() = default;
    derived_forward(derived_forward&& rhs) : base(std::forward<base>(rhs)) {}
};

int main()
{
    derived_move d1;
    derived_move d11 = std::move(d1);

    derived_forward d2;
    derived_forward d22 = std::move(d2);
    return 0;
}


В этом случае первый вариант c move полезет в шаблонный конструктор base, а forward в правильный move конструктор:
base magic constructor
base move constructor
Re[10]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 11:33
Оценка:
Здравствуйте, PM, Вы писали:

Кстати, swap тоже неправильный
PM>
PM>    void swap(fixed_size_function& other)
PM>    {
PM>        using std::swap;
PM>        swap(call_, other.call_);
PM>        swap(copy_, other.copy_);
PM>        swap(move_, other.move_);
PM>        swap(destroy_, other.destroy_);
PM>        swap(storage_, other.storage_);
PM>    }
PM>


Будет работать только что то такое:
        void swap(fixed_size_function& rhs) {
            fixed_size_function tmp = std::move(rhs);
            rhs = std::move(*this);
            *this = std::move(tmp);
        }


non-copyable специализация, кстати, полезна ещё и не только перформанса ради. В версии с копированием независимо от возможной надобности генерируется ф-ция копирования, что не позволяет засовывать в ф-цию movable-only функторы.
Т.е. f(std::bind(foo, unique_ptr<X>(new X()))) не прокатит. А вот в non-copyable версии запросто.

Запилил и copy и move-ability отдельными параметрами шаблона:
  Скрытый текст
#include <functional>

#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif

template<bool Copyable, bool Movable>
struct fixed_function_base;
template<> struct fixed_function_base<true, true> {
    void(*copy_)(const void*, void*) = nullptr;
    void(*move_)(void*, void*) = nullptr;
};
template<> struct fixed_function_base<true, false> {
    void(*copy_)(const void*, void*) = nullptr;
};
template<> struct fixed_function_base<false, true> {
    void(*move_)(void*, void*) = nullptr;
};
template<> struct fixed_function_base<false, false> {
};

template<typename Function, size_t MaxSize, bool Copyable, bool Movable>
class fixed_size_function;

template<size_t MaxSize, bool Copyable, bool Movable, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, Copyable, Movable>
    : fixed_function_base<Copyable, Movable>
{
    template<bool> struct t_bool{};
    typedef t_bool<true> t_true;
    typedef t_bool<false> t_false;
    typedef fixed_function_base<Copyable, Movable> base;
public:
    template<typename F, size_t S, bool C, bool M> fixed_size_function(const fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> fixed_size_function(fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> fixed_size_function(fixed_size_function<F, S, C, M>&&) = delete;
    template<typename F, size_t S, bool C, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> fixed_size_function& operator=(fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> fixed_size_function& operator=(const fixed_size_function<F, S, C, M>&&) = delete;
    template<typename F, size_t S, bool C, bool M> void assign(const fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> void assign(fixed_size_function<F, S, C, M>&) = delete;
    template<typename F, size_t S, bool C, bool M> void assign(fixed_size_function<F, S, C, M>&&) = delete;

    using result_type = Ret;

    static const std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "invalid argument index");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };

    fixed_size_function() = default;

    fixed_size_function(std::nullptr_t) {
    }
    ~fixed_size_function() {
        reset();
    }
    fixed_size_function(const fixed_size_function& rhs) {
        copy_construct_(rhs);
    }
    fixed_size_function(fixed_size_function& rhs) {
        copy_construct_(rhs);
    }
    fixed_size_function(fixed_size_function&& rhs) {
        move_construct_(std::move(rhs), t_bool<Movable>());
    }
    fixed_size_function& operator=(const fixed_size_function& rhs) {
        assign(rhs);
        return *this;
    }
    fixed_size_function& operator=(fixed_size_function& rhs) {
        assign(rhs);
        return *this;
    }
    fixed_size_function& operator=(fixed_size_function&& rhs) {
        assign(std::move(rhs));
        return *this;
    }
    void assign(const fixed_size_function& rhs) {
        reset();
        copy_construct_(rhs);
    }
    void assign(fixed_size_function& rhs) {
        reset();
        copy_construct_(rhs);
    }
    void assign(fixed_size_function&& rhs) {
        reset();
        move_construct_(std::move(rhs), t_bool<Movable>());
    }
    template<typename F>
    fixed_size_function(F&& f) {
        construct_(std::forward<F>(f));
    }
    template<typename F>
    fixed_size_function& operator=(F&& f) {
        reset();
        construct_(std::forward<F>(f));
        return *this;
    }
    template<typename F>
    void assign(F&& f) {
        reset();
        construct_(std::forward<F>(f));
    }

    Ret operator()(Args&& ... args) {
        if(!call_)
            throw std::bad_function_call();
        return call_(&storage_, std::forward<Args>(args)...);
    }

    operator bool() const {
        return call_ != nullptr;
    }
    void reset() {
        auto dtor = dtor_;
        if(dtor) {
            reset_vtable_();
            dtor(&storage_);
        }
    }
    void swap(fixed_size_function& rhs) {
        fixed_size_function tmp = std::move(rhs);
        rhs = std::move(*this);
        *this = std::move(tmp);
    }
private:
    template<typename F> static void dtor_impl_(void* obj) {
        static_cast<F*>(obj)->~F();
    }
    template<typename F> static void move_impl_(void* from, void* to) {
        new(to)F(std::move(*static_cast<F*>(from)));
    }
    template<typename F> static void copy_impl_(const void* from, void* to) {
        new(to)F(*static_cast<const F*>(from));
    }
    template<typename F> static Ret call_impl_(void* f, Args&& ... args) {
        return (*static_cast<F*>(f))(std::forward<Args>(args)...);
    }
    void reset_copy_(t_true) { base::copy_ = nullptr; }
    void reset_copy_(t_false) {}
    void reset_move_(t_true) { base::move_ = nullptr; }
    void reset_move_(t_false) {}
    void reset_vtable_() {
        reset_copy_(t_bool<Copyable>());
        reset_move_(t_bool<Movable>());
        call_ = nullptr;
        dtor_ = nullptr;
    }
    void copy_copy_(const fixed_size_function& rhs, t_true) { base::copy_ = rhs.copy_; }
    void copy_copy_(const fixed_size_function&, t_false) {}
    void copy_move_(const fixed_size_function& rhs, t_true) { base::move_ = rhs.move_; }
    void copy_move_(const fixed_size_function&, t_false) {}
    void copy_vtable_(const fixed_size_function& rhs) {
        copy_copy_(rhs, t_bool<Copyable>());
        copy_move_(rhs, t_bool<Movable>());
        call_ = rhs.call_;
        dtor_ = rhs.dtor_;
    }
    template<typename F> void init_copy_(t_true) { base::copy_ = &copy_impl_<F>; }
    template<typename F> void init_copy_(t_false) {}
    template<typename F> void init_move_(t_true) { base::move_ = &move_impl_<F>; }
    template<typename F> void init_move_(t_false) {}
    template<typename F>
    void construct_(F&& f) {
        typedef typename std::decay<F>::type functor_type;
        static_assert(sizeof(functor_type) <= MaxSize
                      , "non sufficient fixed_size_function storage size");
        new (&storage_) functor_type(std::forward<F>(f));
        init_copy_<F>(t_bool<Copyable>());
        init_move_<F>(t_bool<Movable>());
        call_ = &call_impl_<functor_type, Args...>;
        dtor_ = &dtor_impl_<functor_type>;
    }
    void move_construct_(fixed_size_function&& rhs, t_true) {
        if(!rhs.move_) return;
        rhs.move_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
        rhs.reset();
    }
    void move_construct_(const fixed_size_function& rhs, t_false) {
        copy_construct_(rhs);
    }
    void copy_construct_(const fixed_size_function& rhs) {
        if(!rhs.copy_) return;
        rhs.copy_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
    }
    Ret(*call_)(void*, Args...) = nullptr;
    void(*dtor_)(void*) = nullptr;
    typename std::aligned_storage<MaxSize>::type storage_;
};

template<typename F, size_t S, bool C>
inline void swap(fixed_size_function<F, S, C, true>& lhs, fixed_size_function<F, S, C, true>& rhs) {
    lhs.swap(rhs);
}
template<typename F, size_t S, bool C, bool M>
inline bool operator==(std::nullptr_t, fixed_size_function<F, S, C, M> const& f) {
    return !f;
}
template<typename F, size_t S, bool C, bool M>
inline bool operator==(fixed_size_function<F, S, C, M> const& f, std::nullptr_t) {
    return !f;
}
template<typename F, size_t S, bool C, bool M>
inline bool operator!=(std::nullptr_t, fixed_size_function<F, S, C, M> const& f) {
    return f;
}
template<typename F, size_t S, bool C, bool M>
inline bool operator!=(fixed_size_function<F, S, C, M> const& f, std::nullptr_t) {
    return f;
}

#ifdef _MSC_VER
#pragma warning( pop )
#endif
Re[11]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 11:52
Оценка:
Здравствуйте, sokel, Вы писали:

S>Запилил и copy и move-ability отдельными параметрами шаблона:


Хммм... а можно ещё контроль copy-move ability в рантайм перенести:
  Скрытый текст
    template<typename F>
    void construct_(F&& f) {
        typedef typename std::decay<F>::type functor_type;
        static_assert(sizeof(functor_type) <= MaxSize
                      , "non sufficient fixed_size_function storage size");
        new (&storage_) functor_type(std::forward<F>(f));
        init_copy_<functor_type>(t_bool<Copyable && std::is_copy_constructible<functor_type>::value>());
        init_move_<functor_type>(t_bool<Movable && std::is_move_constructible<functor_type>::value>());
        call_ = &call_impl_<functor_type, Args...>;
        dtor_ = &dtor_impl_<functor_type>;
    }
    void move_construct_(fixed_size_function&& rhs, t_true) {
        if(!rhs.call_) return;
        if(!rhs.move_) return copy_construct_(rhs, t_bool<Copyable>());
        rhs.move_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
        rhs.reset();
    }
    void move_construct_(const fixed_size_function& rhs, t_false) {
        copy_construct_(rhs);
    }
    void copy_construct_(const fixed_size_function& rhs, t_true) {
        copy_construct_(rhs);
    }
    void copy_construct_(const fixed_size_function& rhs, t_false) {
        throw std::bad_function_call();
    }
    void copy_construct_(const fixed_size_function& rhs) {
        if(!rhs.call_) return;
        if(!rhs.copy_) 
            throw std::bad_function_call();
        rhs.copy_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
    }
Re[13]: non-heap std::function. велосипед?
От: uzhas Ниоткуда  
Дата: 12.08.14 12:06
Оценка: +1
Здравствуйте, sokel, Вы писали:

S>Опять таки move это же не move, а просто каст к rvalue ссылке. Страуструп вон ругался, плохое говорит название.

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

  варианты
S>Да вот например, пишем move конструктор для развесистого типа (ну вот не устраивает нас почему то default):
S>
S>struct derived : base {
S>    derived() = default;
S>    derived(derived&& rhs)
S>        : base(std::move(rhs))
S>        , member(std::move(rhs.member))
S>    {}
S>    member_type member;
S>};
S>

S>При инициализации базы мы "переместили" rhs? А как же тогда мы потом инициализируем member?


S>А вот forward так не режет глаз:

S>
S>struct derived : base {
S>    derived() = default;
S>    derived(derived&& rhs)
S>        : base(std::forward<base>(rhs))
S>        , member(std::forward<member_type>(rhs.member))
S>    {}
S>    member_type member;
S>};
S>

оба варианта не айс
я бы предложил в таком духе:
struct derived : base {
  derived() = default;
  derived(derived&& rhs)
    : base(std::move(static_cast<base&>(rhs))) // <-- move base
    , member(std::move(rhs.member)) // <-- move member
    {}
    member_type member;
};


std::forward режет (мой) глаз, когда не прокидывает выведенные шаблонные типы
я привел ссылки, где это описывается и я согласен с мнением на SO
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.