Re[12]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 12:25
Оценка:
Здравствуйте, sokel, Вы писали:

S>Хммм... а можно ещё контроль copy-move ability в рантайм перенести:


Мдя, чорта с два. is_copy/move_constructible просто проверяет наличие соответствующих конструкторов, но не их инстанциируемость...
Т.е. тот же bind result всегда имеет конструктор копирования, но не факт что инстанциируемый, если туда засунуть move-only аругменты.

struct X
{
    X() = default;
    X(const X&) = default;
    std::unique_ptr<int> i;
};
int main()
{
    static_assert(!std::is_copy_constructible<X>::value, "X is copy constructible???");
    return 0;
}
Re[13]: non-heap std::function. велосипед?
От: sokel Россия  
Дата: 12.08.14 13:09
Оценка: 12 (1)
Здравствуйте, sokel, Вы писали:

Сделал управление генерацией move/copy конструкторов через enum-стратегию (none/move/copy/move_and_copy).
  Скрытый текст
enum class construct_type {
    none,
    copy,
    move,
    copy_and_move
};

template<construct_type>
struct fixed_function_base;
template<> struct fixed_function_base<construct_type::copy_and_move> {
    void(*copy_)(const void*, void*) = nullptr;
    void(*move_)(void*, void*) = nullptr;
};
template<> struct fixed_function_base<construct_type::copy> {
    void(*copy_)(const void*, void*) = nullptr;
};
template<> struct fixed_function_base<construct_type::move> {
    void(*move_)(void*, void*) = nullptr;
};
template<> struct fixed_function_base<construct_type::none> {
};

template<typename Function, size_t MaxSize, construct_type ConstructStrategy>
class fixed_size_function;

template<size_t MaxSize, construct_type ConstructStrategy, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, ConstructStrategy>
    : fixed_function_base<ConstructStrategy>
{
    typedef fixed_function_base<ConstructStrategy> base;
    const static bool movable = ConstructStrategy == construct_type::move || ConstructStrategy == construct_type::copy_and_move;
    const static bool copyable = ConstructStrategy == construct_type::copy || ConstructStrategy == construct_type::copy_and_move;
    template<bool> struct movable_flag{};
    typedef movable_flag<movable> is_movable_t;
    typedef movable_flag<true> movable_t;
    typedef movable_flag<false> non_movable_t;
    template<bool> struct copyable_flag{};
    typedef copyable_flag<copyable> is_copyable_t;
    typedef copyable_flag<true> copyable_t;
    typedef copyable_flag<false> non_copyable_t;
public:
    template<typename F, size_t S, construct_type C> fixed_size_function(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function(fixed_size_function<F, S, C>&&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(const fixed_size_function<F, S, C>&&) = delete;
    template<typename F, size_t S, construct_type C> void assign(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> void assign(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> void assign(fixed_size_function<F, S, C>&&) = 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), is_movable_t());
    }
    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), is_movable_t());
    }
    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_(copyable_t) { base::copy_ = nullptr; }
    void reset_copy_(non_copyable_t) {}
    void reset_move_(movable_t) { base::move_ = nullptr; }
    void reset_move_(non_movable_t) {}
    void reset_vtable_() {
        reset_copy_(is_copyable_t());
        reset_move_(is_movable_t());
        call_ = nullptr;
        dtor_ = nullptr;
    }
    void copy_copy_(const fixed_size_function& rhs, copyable_t) { base::copy_ = rhs.copy_; }
    void copy_copy_(const fixed_size_function&, non_copyable_t) {}
    void copy_move_(const fixed_size_function& rhs, movable_t) { base::move_ = rhs.move_; }
    void copy_move_(const fixed_size_function&, non_movable_t) {}
    void copy_vtable_(const fixed_size_function& rhs) {
        copy_copy_(rhs, is_copyable_t());
        copy_move_(rhs, is_movable_t());
        call_ = rhs.call_;
        dtor_ = rhs.dtor_;
    }
    template<typename F> void init_copy_(copyable_t) { base::copy_ = &copy_impl_<F>; }
    template<typename F> void init_copy_(non_copyable_t) {}
    template<typename F> void init_move_(movable_t) { base::move_ = &move_impl_<F>; }
    template<typename F> void init_move_(non_movable_t) {}
    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>(is_copyable_t());
        init_move_<functor_type>(is_movable_t());
        call_ = &call_impl_<functor_type, Args...>;
        dtor_ = &dtor_impl_<functor_type>;
    }
    void move_construct_(fixed_size_function&& rhs, movable_t) {
        if(!rhs.move_) return;
        rhs.move_(&rhs.storage_, &storage_);
        copy_vtable_(rhs);
        rhs.reset();
    }
    void move_construct_(const fixed_size_function& rhs, non_movable_t) {
        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, construct_type C>
inline void swap(fixed_size_function<F, S, C>& lhs, fixed_size_function<F, S, C>& rhs) {
    lhs.swap(rhs);
}
template<typename F, size_t S, construct_type C>
inline bool operator==(std::nullptr_t, fixed_size_function<F, S, C> const& f) {
    return !f;
}
template<typename F, size_t S, construct_type C>
inline bool operator==(fixed_size_function<F, S, C> const& f, std::nullptr_t) {
    return !f;
}
template<typename F, size_t S, construct_type C>
inline bool operator!=(std::nullptr_t, fixed_size_function<F, S, C> const& f) {
    return f;
}
template<typename F, size_t S, construct_type C>
inline bool operator!=(fixed_size_function<F, S, C> const& f, std::nullptr_t) {
    return f;
}


Вот для std function тоже полезной была бы такая штука наверное, тогда необязательным было бы требование copy-constructible функтора. Кстати, через bind placeholders при этом протаскивать move-only типы можно.
Ещё бы bind какой нибудь bind специальный, одноразовый, который аргументам бы move делал при вызове, а то только по ссылке выцеплять можно...
Re[14]: non-heap std::function. велосипед?
От: PM  
Дата: 13.08.14 07:08
Оценка:
Здравствуйте, sokel, Вы писали:

S>Сделал управление генерацией move/copy конструкторов через enum-стратегию (none/move/copy/move_and_copy).


Итересно, спасибо! Если добавить документацию и сравнение с std::function по скорости, то можно и правда в Boost предложить

Небольшое замечание: потерялся explicit у operator bool()

Еще может быть стоит перенести в специализации fixed_function_base функции reset_copy_, reset_move_ copy_copy_, copy_move_ b и т. п.? Т.е. сделать fixed_function_base хранилищем vtable и использовать его конструктор копирования вместо copy_vtable_. И вместо наследования от fixed_function_base агрегировать fixed_function_vtable:

template<typename Ret, typename ...Args>
struct fixed_function_vtable_base
{
    Ret(*call_)(void*, Args...) = nullptr;
    void(*dtor_)(void*) = nullptr;
};

template<construct_type ConstructStrategy, typename Ret, typename ...Args>
struct fixed_function_vtable;

template<typename Ret, typename ...Args>
struct fixed_function_vtable<none> : fixed_function_vtable_base<Ret, Args...>
{
};

template<typename Ret, typename ...Args>
struct fixed_function_vtable<copy> : fixed_function_vtable_base<Ret, Args...>
{
    void(*copy_)(const void*, void*) = nullptr;
};

template<typename Ret, typename ...Args>
struct fixed_function_vtable<move> : fixed_function_vtable_base<Ret, Args...>
{
    void(*move_)(const void*, void*) = nullptr;
};

template<typename Ret, typename ...Args>
struct fixed_function_vtable<copy_and_move> : fixed_function_vtable_base<Ret, Args...>
{
    void(*copy_)(const void*, void*) = nullptr;
    void(*move_)(const void*, void*) = nullptr;
};

//  Тогда в fixed_size_function будет так:
template<size_t MaxSize, construct_type ConstructStrategy, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, ConstructStrategy>
{
...

    typedef fixed_function_vtable<ConstructStrategy, Ret, Args..> vtable;
    typedef std::aligned_storage<MaxSize>::type storage;

    vtable vtbl_;
    storage storage_;

    void reset_vtable_()
    {
        vtbl_ = vtable();
    }

    void copy_vtable_(const fixed_size_function& rhs)
    {
        vtbl_ = rhs.vtbl_;
    }


S>Вот для std function тоже полезной была бы такая штука наверное, тогда необязательным было бы требование copy-constructible функтора. Кстати, через bind placeholders при этом протаскивать move-only типы можно.

S>Ещё бы bind какой нибудь bind специальный, одноразовый, который аргументам бы move делал при вызове, а то только по ссылке выцеплять можно...

Я так понимаю, при наличии лямбд bind уже мало кому интересен. Тем более, кажется в C++14 добавили возможность move для захватываемых аргументов лямбды.
Re[15]: non-heap std::function. велосипед?
От: PM  
Дата: 13.08.14 23:22
Оценка:
Здравствуйте, PM, Вы писали:


PM>Еще может быть стоит перенести в специализации fixed_function_base функции reset_copy_, reset_move_ copy_copy_, copy_move_ b и т. п.? Т.е. сделать fixed_function_base хранилищем vtable и использовать его конструктор копирования вместо copy_vtable_. И вместо наследования от fixed_function_base агрегировать fixed_function_vtable:


Сделал так, структура fixed_function_vtable спрятана внутри fixed_size_function:

enum class construct_type {
    none,
    copy,
    move,
    copy_and_move
};

template<typename Function, size_t MaxSize, construct_type ConstructStrategy = construct_type::copy_and_move>
class fixed_size_function;

template<size_t MaxSize, construct_type ConstructStrategy, typename Ret, typename ...Args>
class fixed_size_function<Ret(Args...), MaxSize, ConstructStrategy>
{
    using is_copyable = std::integral_constant<bool, ConstructStrategy == construct_type::copy
        || ConstructStrategy == construct_type::copy_and_move>;
    using is_movable = std::integral_constant<bool, ConstructStrategy == construct_type::move
        || ConstructStrategy == construct_type::copy_and_move>;
public:
    template<typename F, size_t S, construct_type C> fixed_size_function(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function(fixed_size_function<F, S, C>&&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> fixed_size_function& operator=(const fixed_size_function<F, S, C>&&) = delete;
    template<typename F, size_t S, construct_type C> void assign(const fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> void assign(fixed_size_function<F, S, C>&) = delete;
    template<typename F, size_t S, construct_type C> void assign(fixed_size_function<F, S, C>&&) = 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), is_movable());
    }
    fixed_size_function& operator=(std::nullptr_t) {
        reset();
        return *this;
    }
    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), is_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 (!vtable_.call_)
            throw std::bad_function_call();
        return vtable_.call_(&storage_, std::forward<Args>(args)...);
    }

    explicit operator bool() const {
        return vtable_.call_ != nullptr;
    }
    void reset() {
        auto dtor = vtable_.dtor_;
        if (dtor) {
            vtable_ = 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:
    struct fixed_function_vtable_base
    {
        Ret(*call_)(void*, Args&& ...) = nullptr;
        void(*dtor_)(void*) = nullptr;
    };

    template<construct_type ConstructStrategy>
    struct fixed_function_vtable;

    template<>
    struct fixed_function_vtable<construct_type::none>
        : fixed_function_vtable_base
    {
    };

    template<>
    struct fixed_function_vtable<construct_type::copy>
        : fixed_function_vtable_base
    {
        void(*copy_)(const void*, void*) = nullptr;
    };

    template<>
    struct fixed_function_vtable<construct_type::move>
        : fixed_function_vtable_base
    {
        void(*move_)(void*, void*) = nullptr;
    };

    template<>
    struct fixed_function_vtable<construct_type::copy_and_move>
        : fixed_function_vtable_base
    {
        void(*copy_)(const void*, void*) = nullptr;
        void(*move_)(void*, void*) = nullptr;
    };

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

    template<typename F> void init_copy_(std::true_type copyable) { vtable_.copy_ = &copy_impl_<F>; }
    template<typename F> void init_copy_(std::false_type copyable) {}
    template<typename F> void init_move_(std::true_type movable) { vtable_.move_ = &move_impl_<F>; }
    template<typename F> void init_move_(std::false_type movable) {}

    template<typename F>
    void construct_(F&& f) {
        using functor_type = std::decay<F>::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>(is_copyable());
        init_move_<functor_type>(is_movable());
        vtable_.call_ = &call_impl_<functor_type>;
        vtable_.dtor_ = &dtor_impl_<functor_type>;
    }
    void move_construct_(fixed_size_function&& rhs, std::true_type movable) {
        if (!rhs.vtable_.move_) return;
        rhs.vtable_.move_(&rhs.storage_, &storage_);
        vtable_ = rhs.vtable_;
        rhs.reset();
    }
    void move_construct_(const fixed_size_function& rhs, std::false_type movable) {
        copy_construct_(rhs);
    }

    void copy_construct_(const fixed_size_function& rhs) {
        if (!rhs.vtable_.copy_) return;
        rhs.vtable_.copy_(&rhs.storage_, &storage_);
        vtable_ = rhs.vtable_;
    }

    using vtable = fixed_function_vtable<ConstructStrategy>;
    using storage = typename std::aligned_storage<MaxSize>::type;

    vtable vtable_;
    storage storage_;
};

template<typename F, size_t S, construct_type C>
inline void swap(fixed_size_function<F, S, C>& lhs, fixed_size_function<F, S, C>& rhs) {
    lhs.swap(rhs);
}
template<typename F, size_t S, construct_type C>
inline bool operator==(std::nullptr_t, fixed_size_function<F, S, C> const& f) {
    return !f;
}
template<typename F, size_t S, construct_type C>
inline bool operator==(fixed_size_function<F, S, C> const& f, std::nullptr_t) {
    return !f;
}
template<typename F, size_t S, construct_type C>
inline bool operator!=(std::nullptr_t, fixed_size_function<F, S, C> const& f) {
    return f;
}
template<typename F, size_t S, construct_type C>
inline bool operator!=(fixed_size_function<F, S, C> const& f, std::nullptr_t) {
    return f;
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.