#include <utility>

enum access_flags {
    readable  = 1 << 0,
    writeable = 1 << 1
};

template <typename ValueT>
class default_accessor {
public:
    // Types.
    typedef ValueT value_type;
    typedef value_type& reference;
    typedef const value_type& const_reference;

    // Constructors.
    default_accessor(const_reference value_ref) : m_value_ref(value_ref)
    {
        // Do nothing.
    }

    // Accessors.
    const_reference operator()() const noexcept
    {
        return m_value_ref;
    }
private:
    // Data.
    const_reference m_value_ref;
};

template <typename ValueT>
class default_mutator {
public:
    // Types.
    typedef ValueT value_type;
    typedef value_type& reference;
    typedef const value_type& const_reference;

    // Constructors.
    default_mutator(reference value_ref) : m_value_ref(value_ref)
    {
        // Do nothing.
    }

    // Mutators.
    void operator()(const_reference value) noexcept
    {
        m_value_ref = value;
    }
    
    void operator()(value_type&& value) noexcept
    {
        m_value_ref = std::move(value);
    }
private:
    // Data.
    reference m_value_ref;
};

template <typename ValueT, unsigned AccessFlags = readable | writeable, class Accessor = default_accessor<ValueT>,
          class Mutator = default_mutator<ValueT>>
class property {
public:
    // Types.
    typedef ValueT value_type;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef Accessor accessor_type;
    typedef Mutator mutator_type;

    // Constructors.
    property() : m_accessor(m_value), m_mutator(m_value)
    {
        // Do nothing.
    }

    property(accessor_type accessor, mutator_type mutator) : m_accessor(accessor), m_mutator(mutator)
    {
        // Do nothing.
    }

    property(accessor_type accessor) : m_accessor(accessor), m_mutator(m_value)
    {
        // Do nothing.
    }

    property(mutator_type mutator) : m_accessor(m_value), m_mutator(mutator)
    {
        // Do nothing.
    }

    property(reference value_ref) : m_accessor(value_ref), m_mutator(value_ref)
    {
        // Do nothing.
    }

    // Accessors.
    const_reference get() const noexcept
    {
        static_assert((AccessFlags & readable) == readable, "Can't read write-only property.");
        return m_accessor();
    }
    
    const_reference get() noexcept
    {
        static_assert((AccessFlags & readable) == readable, "Can't read write-only property.");
        return m_accessor();
    }

    // Mutators.
    void set(const_reference value)
    {
        static_assert((AccessFlags & readable) == readable, "Can't write to read-only property.");
        m_mutator(value);
    }
    
    void set(value_type&& value)
    {
        static_assert((AccessFlags & readable) == readable, "Can't write to read-only property.");
        m_mutator(value);
    }

    property& operator=(const_reference rhs)
    {
        const property& lhs(*this);
        lhs.set(rhs);
        return lhs;
    }

    property& operator=(const property& rhs)
    {
        const property& lhs(*this);
        lhs.set(rhs.get());
        return lhs;
    }
private:
    // Data.
    ValueT m_value;
    accessor_type m_accessor;
    mutator_type m_mutator;
};

#include <iostream>

/// Custom accessor.
class my_accessor {
public:
    my_accessor(const int& value_ref, int& counter_ref) : m_value_ref(value_ref), m_counter_ref(counter_ref)
    {
        // Do nothing.
    }

    const int& operator()() noexcept
    {
        ++m_counter_ref;
        return m_value_ref;
    }
private:
    const int& m_value_ref;
    int& m_counter_ref;
};

/// Example showing a read-only property with a custom accessor which increments a counter whenever the property is
/// accessed.
class foo {
public:
    property<int, readable, my_accessor> readonly;
    property<int, readable> times_accessed;

    foo(int value) : m_readonly(value), m_times_accessed(0), readonly(my_accessor(m_readonly, m_times_accessed)),
                     times_accessed(m_times_accessed)
    {
        // Do nothing.
    }
private:
    int m_readonly;
    int m_times_accessed;
};

int main()
{
    foo object(5);
    for (int i = 0; i < 5; ++i) {
        std::cout << "Times accessed: " << object.times_accessed.get() << '\n';
        int tmp = object.readonly.get();
    }
    std::cout << "Times accessed: " << object.times_accessed.get() << std::endl;
    return 0;
}