Home > database >  Custom std::any implementation: template copy and move constructors overloading problem
Custom std::any implementation: template copy and move constructors overloading problem

Time:11-16

I've written my own std::any implementation that has configurable small value optimization (STL has no configuration for this and default values are very small, e. g. in GCC it's just one pointer size).

#pragma once
 
#include <cstddef>
#include <cstring>
#include <typeinfo>
#include <new>
#include <utility>
 
template<std::size_t STORAGE_SIZE> class Any {
    typedef void (*CopyFunc)(void *dst, const void *src);
    typedef void (*MoveFunc)(void *dst, void *src);
    typedef void(*DestroyFunc)(void *val);
    
    struct Operations {
        const std::type_info &typeInfo;
        std::size_t size;
        std::size_t align;
        CopyFunc copy;
        MoveFunc move;
        DestroyFunc destroy;
    };
    
    template<typename T> static inline constexpr Operations OPERATIONS = {
            .typeInfo = typeid(T),
            .size = ([]() {
                if constexpr (std::is_same_v<T, void>) {
                    return 0;
                } else {
                    return sizeof(T);
                }
            })(),
            .align = ([]() {
                if constexpr (std::is_same_v<T, void>) {
                    return 0;
                } else {
                    return alignof(T);
                }
            })(),
            .copy = ([]() {
                if constexpr (std::is_same_v<T, void> || std::is_trivially_copy_constructible_v<T>) {
                    return nullptr;
                } else {
                    return static_cast<CopyFunc>([](void *dst, const void *src) {
                        new (dst) T(*reinterpret_cast<const T*>(src));
                    });
                }
            })(),
            .move = ([]() {
                if constexpr (std::is_same_v<T, void> || std::is_trivially_move_constructible_v<T>) {
                    return nullptr;
                } else {
                    return static_cast<MoveFunc>([](void *dst, void *src) {
                        new (dst) T(std::move(*reinterpret_cast<T*>(src)));
                    });
                }
            })(),
            .destroy = ([]() {
                if constexpr (std::is_same_v<T, void> || std::is_trivially_destructible_v<T>) {
                    return nullptr;
                } else {
                    return static_cast<DestroyFunc>([](void *arg) {
                        reinterpret_cast<T*>(arg)->~T();
                    });
                }
            })()
    };
    
    std::size_t m_storage[(STORAGE_SIZE   sizeof(std::size_t) - 1) / sizeof(std::size_t)];
    void *m_heapStorage = nullptr;
    const Operations *m_operations = &OPERATIONS<void>;
 
public:
    Any() = default;
    
    template<typename T> Any(const T &value) {
        if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
            m_heapStorage = new T(value);
        } else {
            new (m_storage) T(value);
        }
        m_operations = &OPERATIONS<T>;
    }
    
    template<typename T> Any(T &&value) noexcept {
        if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
            m_heapStorage = new T(std::move(value));
        } else {
            new (m_storage) T(std::move(value));
        }
        m_operations = &OPERATIONS<T>;
    }
    
    template<std::size_t N> Any(const Any<N> &value) {
        if (value.m_heapStorage || value.m_operations->size > sizeof(m_storage)) {
            m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
            if (value.m_operations) {
                value.m_operations->copy(m_heapStorage, value.m_heapStorage ? value.m_heapStorage : value.m_storage);
            } else {
                std::memcpy(
                        m_heapStorage,
                        value.m_heapStorage ? value.m_heapStorage : value.m_storage,
                        value.m_operations->size
                );
            }
        } else {
            if (value.m_operations->copy) {
                value.m_operations->copy(m_storage, value.m_storage);
            } else {
                std::memcpy(m_storage, value.m_storage, value.m_operations->size);
            }
        }
        m_operations = value.m_operations;
    }
    
    template<std::size_t N> Any(Any<N> &&value) noexcept {
        m_operations = value.m_operations;
        if (value.m_heapStorage) {
            m_heapStorage = value.m_heapStorage;
            value.m_heapStorage = nullptr;
            value.m_operations = &OPERATIONS<void>;
        } else {
            if (value.m_operations->size > STORAGE_SIZE) {
                m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
            }
            if (m_operations->move) {
                m_operations->move(m_heapStorage ? m_heapStorage : m_storage, value.m_storage);
            } else {
                std::memcpy(m_heapStorage ? m_heapStorage : m_storage, value.m_storage, value.m_operations->size);
            }
        }
    }
    
    ~Any() {
        if (m_operations->destroy) {
            m_operations->destroy(m_heapStorage ? m_heapStorage : m_storage);
        }
        if (m_heapStorage) {
            delete static_cast<char*>(m_heapStorage);
        }
    }
    
    [[nodiscard]] const std::type_info &type() const {
        return m_operations->typeInfo;
    }
    
    [[nodiscard]] std::size_t size() const {
        return m_operations->size;
    }
    
    [[nodiscard]] std::size_t align() const {
        return m_operations->align;
    }
    
    [[nodiscard]] bool isHeapAllocated() const {
        return m_heapStorage != nullptr;
    }
    
    template<typename T> [[nodiscard]] const T *get() const {
        if (m_operations->typeInfo == typeid(T)) {
            return reinterpret_cast<const T*>(m_heapStorage ? m_heapStorage : m_storage);
        } else {
            return nullptr;
        }
    }
    
    template<typename T> [[nodiscard]] T *get() {
        if (m_operations->typeInfo == typeid(T)) {
            return reinterpret_cast<T*>(m_heapStorage ? m_heapStorage : m_storage);
        } else {
            return nullptr;
        }
    }
    
    void clear() {
        if (m_operations->destroy) {
            m_operations->destroy(m_heapStorage ? m_heapStorage : m_storage);
        }
        if (m_heapStorage) {
            delete static_cast<char*>(m_heapStorage);
            m_heapStorage = nullptr;
        }
        m_operations = &OPERATIONS<void>;
    }
    
    template<typename T, typename... Args> void emplace(Args &&...args) {
        clear();
        if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
            m_heapStorage = new T(std::forward<Args...>(args)...);
        } else {
            new (m_storage) T(std::forward<Args...>(args)...);
        }
        m_operations = &OPERATIONS<T>;
    }
    
    template<std::size_t N> Any<STORAGE_SIZE> &operator=(const Any<N> &value) {
        clear();
        if (value.m_heapStorage || value.m_operations->size > sizeof(m_storage)) {
            m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
            if (value.m_operations) {
                value.m_operations->copy(m_heapStorage, value.m_heapStorage ? value.m_heapStorage : value.m_storage);
            } else {
                std::memcpy(
                        m_heapStorage,
                        value.m_heapStorage ? value.m_heapStorage : value.m_storage,
                        value.m_operations->size
                );
            }
        } else {
            if (value.m_operations->copy) {
                value.m_operations->copy(m_storage, value.m_storage);
            } else {
                std::memcpy(m_storage, value.m_storage, value.m_operations->size);
            }
        }
        m_operations = value.m_operations;
        return *this;
    }
    
    template<std::size_t N> Any<STORAGE_SIZE> &operator=(Any<N> &&value) noexcept {
        clear();
        m_operations = value.m_operations;
        if (value.m_heapStorage) {
            m_heapStorage = value.m_heapStorage;
            value.m_heapStorage = nullptr;
            value.m_operations = &OPERATIONS<void>;
        } else {
            if (value.m_operations->size > STORAGE_SIZE) {
                m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
            }
            if (m_operations->move) {
                m_operations->move(m_heapStorage ? m_heapStorage : m_storage, value.m_storage);
            } else {
                std::memcpy(m_heapStorage ? m_heapStorage : m_storage, value.m_storage, value.m_operations->size);
            }
        }
        return *this;
    }
    
    template<typename T> Any &operator=(T &&value) noexcept {
        using TT = typename std::decay<T>::type;
        emplace<TT>(std::forward<T>(value));
        return *this;
    }
};

But I have problem with some constructors:

int x = 10;
Any<64> v0 = x; // Error

Any<64> v1 = 10; // Works
Any<128> v2 = v1; // Error

In both error cases compiler picks template<typename T> Any(T &&value) instead of template<typename T> Any(const T &value) for the first case and template<std::size_t N> Any(const Any<N> &value) for the second.

How constructors overloading can be fixed?

P. S.: There is the same problem for an assignment operator when we pass Any instead of some value (for value everything works fine).

CodePudding user response:

The main issue is that template<typename T> Any(T &&value) is a forwarding/universal reference and can accept anything. In certain situations it might prefer this constructor over any kind of constructor from Any. So I'd recommend conditioning it so it won't be an Any. Also, since it accepts any input you can unify it with the const T& constructor into a single one - just use std::forward.

template<typename T, std::enable_if_t<!is_Any_v<std::remove_reference_t<std::remove_const_t<T> > >, int> = 0> 
Any(T &&value) noexcept {
    if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
        m_heapStorage = new T(std::forward<T>(value));
        } else {
            new (m_storage) T(std::forward<T>(value));
        }
        m_operations = &OPERATIONS<T>;
}

whereas is_Any is defined above the class definition as:

template<std::size_t STORAGE_SIZE> class Any;

template<typename T>
struct is_Any : std::false_type {};

template<std::size_t STORAGE_SIZE>
struct is_Any<Any<STORAGE_SIZE>> : std::true_type {};

template<typename T>
constexpr bool is_Any_v = is_Any<T>::value;

CodePudding user response:

You can simply enable_if non-references:

template<typename T, std::enable_if_t<!std::is_reference_v<T>, bool> = true>
Any(T &&value) noexcept {
// ...

Note that you will also need to move Operations out of the class.

Demo

  •  Tags:  
  • c
  • Related