/*
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at
 * https://www.boost.org/LICENSE_1_0.txt)
 *
 * Copyright (c) 2023-2024 Andrey Semashev
 */
/*!
 * \file   unique_resource.cpp
 * \author Andrey Semashev
 *
 * \brief  This file contains tests for \c unique_resource.
 */

#include <boost/scope/unique_resource.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/core/lightweight_test_trait.hpp>
#include <boost/config.hpp>
#include <ostream>
#include <utility>
#include <stdexcept>

template< typename Resource >
struct empty_resource_deleter
{
    void operator() (Resource const&) const noexcept
    {
    }
};

template< typename Resource >
inline void copy_resource(Resource const& from, Resource& to)
{
    to = from;
}

template< typename Resource >
class checking_resource_deleter
{
private:
    Resource* m_deleted;
    int& m_n;

public:
    explicit checking_resource_deleter(int& n) noexcept :
        m_deleted(nullptr),
        m_n(n)
    {
    }

    explicit checking_resource_deleter(Resource& deleted, int& n) noexcept :
        m_deleted(&deleted),
        m_n(n)
    {
    }

    checking_resource_deleter(checking_resource_deleter&& that) noexcept :
        m_deleted(that.m_deleted),
        m_n(that.m_n)
    {
    }

    checking_resource_deleter(checking_resource_deleter const& that) noexcept :
        m_deleted(that.m_deleted),
        m_n(that.m_n)
    {
    }

    // Make sure the deleter is move and copy-assignable
    checking_resource_deleter& operator= (checking_resource_deleter&& that) noexcept
    {
        m_deleted = that.m_deleted;
        return *this;
    }

    checking_resource_deleter& operator= (checking_resource_deleter const& that) noexcept
    {
        m_deleted = that.m_deleted;
        return *this;
    }

    Resource* get_deleted() const noexcept
    {
        return m_deleted;
    }

    void operator() (Resource const& res) const noexcept
    {
        if (m_deleted)
            copy_resource(res, *m_deleted);
        ++m_n;
    }
};

int g_n = 0, g_res1 = 0, g_res2 = 0;

void check_int()
{
    {
        boost::scope::unique_resource< int, empty_resource_deleter< int > > ur;
        BOOST_TEST_EQ(ur.get(), 0);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }

    int n = 0;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ boost::scope::default_resource, checking_resource_deleter< int >(n) };
        BOOST_TEST_EQ(ur.get(), 0);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }
    BOOST_TEST_EQ(n, 0);

    {
        boost::scope::unique_resource< int, empty_resource_deleter< int > > ur{ 10 };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!!ur);
    }

    int deleted_res1 = -1;
    n = 0;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ 0, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 0);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, 0);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST_EQ(ur.get_deleter().get_deleted(), &deleted_res1);
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, 10);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        ur.release();
        BOOST_TEST(!ur.allocated());
    }
    BOOST_TEST_EQ(n, 0);
    BOOST_TEST_EQ(deleted_res1, -1);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST_EQ(noexcept(ur.reset()), noexcept(std::declval< checking_resource_deleter< int >& >()(10)));
        ur.reset();
        BOOST_TEST(!ur.allocated());
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, 10);
    }
    BOOST_TEST_EQ(n, 1);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        ur.reset(20);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, 10);
        deleted_res1 = -1;
        BOOST_TEST_EQ(ur.get(), 20);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 20);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur2{ std::move(ur1) };
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, 10);

    n = 0;
    deleted_res1 = -1;
    int deleted_res2 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur2{ 20, checking_resource_deleter< int >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST(ur2.allocated());
        ur2 = std::move(ur1);
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, -1);
        BOOST_TEST_EQ(deleted_res2, 20);
        deleted_res2 = -1;
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 10);
    BOOST_TEST_EQ(deleted_res2, -1);

    {
        boost::scope::unique_resource< int, empty_resource_deleter< int > > ur1;
        BOOST_TEST_EQ(ur1.get(), 0);
        BOOST_TEST(!ur1.allocated());
        boost::scope::unique_resource< int, empty_resource_deleter< int > > ur2{ 10, empty_resource_deleter< int >() };
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), 0);
        BOOST_TEST(!ur2.allocated());
    }

    n = 0;
    deleted_res1 = -1;
    deleted_res2 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int > > ur2{ 20, checking_resource_deleter< int >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(n, 0);
        BOOST_TEST_EQ(ur1.get(), 20);
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur2.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 10);
    BOOST_TEST_EQ(deleted_res2, 20);

    struct local
    {
        static void raw_func_deleter(int)
        {
            ++g_n;
        }

        static void raw_func_deleter1(int res)
        {
            g_res1 = res;
        }

        static void raw_func_deleter2(int res)
        {
            g_res2 = res;
        }
    };

    g_n = 0;
    {
        boost::scope::unique_resource< int, void (&)(int) > ur(10, local::raw_func_deleter);
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!noexcept(ur.reset()));
    }
    BOOST_TEST_EQ(g_n, 1);

    g_n = 0;
    {
        boost::scope::unique_resource< int, void (&)(int) > ur1(10, local::raw_func_deleter);
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, void (&)(int) > ur2(std::move(ur1));
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(g_n, 1);

    g_res1 = 0;
    g_res2 = 0;
    {
        boost::scope::unique_resource< int, void (&)(int) > ur1(10, local::raw_func_deleter1);
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, void (&)(int) > ur2(20, local::raw_func_deleter2);
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST(ur2.allocated());
        ur2 = std::move(ur1);
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(g_res1, 10);
    BOOST_TEST_EQ(g_res2, 20);

    g_res1 = 0;
    g_res2 = 0;
    {
        boost::scope::unique_resource< int, void (&)(int) > ur1(10, local::raw_func_deleter1);
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, void (&)(int) > ur2(20, local::raw_func_deleter2);
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(ur1.get(), 20);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
    }
    BOOST_TEST_EQ(g_res1, 10);
    BOOST_TEST_EQ(g_res2, 20);
}


struct struct_resource
{
    int value;

    struct_resource(int v = 0) noexcept :
        value(v)
    {
    }

    friend bool operator== (struct_resource const& left, struct_resource const& right) noexcept
    {
        return left.value == right.value;
    }

    friend bool operator!= (struct_resource const& left, struct_resource const& right) noexcept
    {
        return !(left == right);
    }

    friend std::ostream& operator<< (std::ostream& strm, struct_resource const& res)
    {
        strm << "{ " << res.value << " }";
        return strm;
    }
};

void check_struct()
{
    {
        boost::scope::unique_resource< struct_resource, empty_resource_deleter< struct_resource > > ur;
        BOOST_TEST_EQ(ur.get(), struct_resource{});
        BOOST_TEST(!ur.allocated());
    }

    int n = 0;
    struct_resource deleted_res1{ -1 };
    {
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur{ struct_resource{ 10 }, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), struct_resource{ 10 });
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    {
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur{ struct_resource{ 10 }, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), struct_resource{ 10 });
        BOOST_TEST(ur.allocated());
        ur.reset(20);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });
        deleted_res1 = struct_resource{ -1 };
        BOOST_TEST_EQ(ur.get(), struct_resource{ 20 });
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 20 });

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    {
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur1{ struct_resource{ 10 }, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), struct_resource{ 10 });
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur2{ std::move(ur1) };
        BOOST_TEST_EQ(ur2.get(), struct_resource{ 10 });
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    struct_resource deleted_res2{ -1 };
    {
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur1{ struct_resource{ 10 }, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), struct_resource{ 10 });
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur2{ struct_resource{ 20 }, checking_resource_deleter< struct_resource >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), struct_resource{ 20 });
        BOOST_TEST(ur2.allocated());
        ur2 = std::move(ur1);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, struct_resource{ -1 });
        BOOST_TEST_EQ(deleted_res2, struct_resource{ 20 });
        deleted_res2 = struct_resource{ -1 };
        BOOST_TEST_EQ(ur2.get(), struct_resource{ 10 });
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });
    BOOST_TEST_EQ(deleted_res2, struct_resource{ -1 });

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    deleted_res2 = struct_resource{ -1 };
    {
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur1{ struct_resource{ 10 }, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), struct_resource{ 10 });
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< struct_resource, checking_resource_deleter< struct_resource > > ur2{ struct_resource{ 20 }, checking_resource_deleter< struct_resource >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), struct_resource{ 20 });
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(n, 0);
        BOOST_TEST_EQ(ur1.get(), struct_resource{ 20 });
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), struct_resource{ 10 });
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur2.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });
    BOOST_TEST_EQ(deleted_res2, struct_resource{ 20 });
}


void check_ptr()
{
    {
        boost::scope::unique_resource< struct_resource*, empty_resource_deleter< struct_resource* > > ur;
        BOOST_TEST_EQ(ur.get(), static_cast< struct_resource* >(nullptr));
        BOOST_TEST(!ur.allocated());
    }

    int n = 0;
    struct_resource res1{ 10 };
    struct_resource* deleted_res1 = nullptr;
    {
        boost::scope::unique_resource< struct_resource*, checking_resource_deleter< struct_resource* > > ur{ &res1, checking_resource_deleter< struct_resource* >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), &res1);
        BOOST_TEST_EQ(ur.get()->value, 10);
        BOOST_TEST_EQ(ur->value, 10);
        BOOST_TEST_EQ((*ur).value, 10);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, &res1);
}


void check_ref()
{
    int n = 0;
    struct_resource res1{ 10 };
    struct_resource deleted_res1{ -1 };
    {
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur{ res1, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), res1);
        BOOST_TEST_EQ(&ur.get(), &res1);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, res1);

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    struct_resource res2{ 20 };
    {
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur{ res1, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(&ur.get(), &res1);
        BOOST_TEST(ur.allocated());
        ur.reset(res2);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, res1);
        deleted_res1 = struct_resource{ -1 };
        BOOST_TEST_EQ(&ur.get(), &res2);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, res2);

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    {
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur1{ res1, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(&ur1.get(), &res1);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur2{ std::move(ur1) };
        BOOST_TEST_EQ(&ur2.get(), &res1);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, res1);

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    struct_resource deleted_res2{ -1 };
    {
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur1{ res1, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(&ur1.get(), &res1);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur2{ res2, checking_resource_deleter< struct_resource >(deleted_res2, n) };
        BOOST_TEST_EQ(&ur2.get(), &res2);
        BOOST_TEST(ur2.allocated());
        ur2 = std::move(ur1);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, struct_resource{ -1 });
        BOOST_TEST_EQ(deleted_res2, struct_resource{ 20 });
        deleted_res2 = struct_resource{ -1 };
        BOOST_TEST_EQ(&ur2.get(), &res1);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });
    BOOST_TEST_EQ(deleted_res2, struct_resource{ -1 });

    n = 0;
    deleted_res1 = struct_resource{ -1 };
    deleted_res2 = struct_resource{ -1 };
    {
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur1{ res1, checking_resource_deleter< struct_resource >(deleted_res1, n) };
        BOOST_TEST_EQ(&ur1.get(), &res1);
        BOOST_TEST(ur1.allocated());
        struct_resource expected_res2{ 20 };
        boost::scope::unique_resource< struct_resource&, checking_resource_deleter< struct_resource > > ur2{ res2, checking_resource_deleter< struct_resource >(deleted_res2, n) };
        BOOST_TEST_EQ(&ur2.get(), &res2);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(n, 0);
        BOOST_TEST_EQ(&ur1.get(), &res2);
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(&ur2.get(), &res1);
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur2.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, struct_resource{ 10 });
    BOOST_TEST_EQ(deleted_res2, struct_resource{ 20 });
}


class throw_on_move_resource
{
private:
    int m_value;

public:
    explicit throw_on_move_resource(int v = 0) noexcept :
        m_value(v)
    {
    }

    throw_on_move_resource(throw_on_move_resource const&) = default;
    throw_on_move_resource& operator= (throw_on_move_resource const&) = default;

    throw_on_move_resource(throw_on_move_resource&&)
    {
        throw std::runtime_error("throw_on_move_resource move ctor");
    }
    throw_on_move_resource& operator= (throw_on_move_resource&&)
    {
        throw std::runtime_error("throw_on_move_resource move assignment");
    }

    int get() const noexcept
    {
        return m_value;
    }

    bool operator== (throw_on_move_resource const& that) const noexcept
    {
        return m_value == that.m_value;
    }

    bool operator!= (throw_on_move_resource const& that) const noexcept
    {
        return !operator==(that);
    }

    friend std::ostream& operator<< (std::ostream& strm, throw_on_move_resource const& res)
    {
        strm << "{ " << res.m_value << " }";
        return strm;
    }
};

class throw_on_copy_resource
{
private:
    int m_value;
    mutable bool m_throw;

public:
    explicit throw_on_copy_resource(int v = 0, bool do_throw = true) noexcept :
        m_value(v),
        m_throw(do_throw)
    {
    }

    throw_on_copy_resource(throw_on_copy_resource const& that) :
        m_value(that.m_value),
        m_throw(that.m_throw)
    {
        if (m_throw)
            throw std::runtime_error("throw_on_copy_resource copy ctor");
    }
    throw_on_copy_resource& operator= (throw_on_copy_resource const& that)
    {
        m_value = that.m_value;
        m_throw = that.m_throw;
        if (m_throw)
            throw std::runtime_error("throw_on_copy_resource copy assignment");
        return *this;
    }

    throw_on_copy_resource(throw_on_copy_resource&&) = delete;
    throw_on_copy_resource& operator= (throw_on_copy_resource&&) = delete;

    int get() const noexcept
    {
        return m_value;
    }

    void set_throw(bool do_throw) const noexcept
    {
        m_throw = do_throw;
    }

    bool operator== (throw_on_copy_resource const& that) const noexcept
    {
        return m_value == that.m_value;
    }

    bool operator!= (throw_on_copy_resource const& that) const noexcept
    {
        return !operator==(that);
    }

    friend std::ostream& operator<< (std::ostream& strm, throw_on_copy_resource const& res)
    {
        strm << "{ " << res.m_value << " }";
        return strm;
    }
};

void check_throw_resource()
{
    int n = 0;
    try
    {
        boost::scope::unique_resource< throw_on_move_resource, checking_resource_deleter< throw_on_move_resource > > ur{ throw_on_move_resource{ 10 }, checking_resource_deleter< throw_on_move_resource >(n) };
        BOOST_TEST_EQ(ur.get().get(), 10);
        BOOST_TEST(ur.allocated());
    }
    catch (...)
    {
        BOOST_ERROR("An exception is not expected to be thrown by throw_on_move_resource (copy ctor should be used)");
    }
    BOOST_TEST_EQ(n, 1);

    n = 0;
    try
    {
        boost::scope::unique_resource< throw_on_copy_resource, checking_resource_deleter< throw_on_copy_resource > > ur{ throw_on_copy_resource{ 10 }, checking_resource_deleter< throw_on_copy_resource >(n) };
        BOOST_ERROR("An exception is expected to be thrown by throw_on_copy_resource");
    }
    catch (...)
    {
    }
    BOOST_TEST_EQ(n, 1);

    n = 0;
    try
    {
        boost::scope::unique_resource< throw_on_copy_resource, checking_resource_deleter< throw_on_copy_resource > > ur1{ throw_on_copy_resource{ 10, false }, checking_resource_deleter< throw_on_copy_resource >(n) };
        ur1.get().set_throw(true);
        try
        {
            boost::scope::unique_resource< throw_on_copy_resource, checking_resource_deleter< throw_on_copy_resource > > ur2 = std::move(ur1);
            BOOST_ERROR("An exception is expected to be thrown by throw_on_copy_resource");
        }
        catch (...)
        {
            BOOST_TEST_EQ(n, 0);
            throw;
        }
    }
    catch (...)
    {
    }
    BOOST_TEST_EQ(n, 1);

    n = 0;
    try
    {
        boost::scope::unique_resource< throw_on_copy_resource, checking_resource_deleter< throw_on_copy_resource > > ur1{ throw_on_copy_resource{ 10, false }, checking_resource_deleter< throw_on_copy_resource >(n) };
        ur1.get().set_throw(true);
        try
        {
            boost::scope::unique_resource< throw_on_copy_resource, checking_resource_deleter< throw_on_copy_resource > > ur2{ throw_on_copy_resource{ 20, false }, checking_resource_deleter< throw_on_copy_resource >(n) };
            ur2 = std::move(ur1);
            BOOST_ERROR("An exception is expected to be thrown by throw_on_copy_resource");
        }
        catch (...)
        {
            BOOST_TEST_EQ(n, 1);
            throw;
        }
    }
    catch (...)
    {
    }
    BOOST_TEST_EQ(n, 2);
}

class moveable_resource
{
private:
    int m_value;

public:
    explicit moveable_resource(int value = 0) noexcept :
        m_value(value)
    {
    }

    moveable_resource(moveable_resource&& that) noexcept :
        m_value(that.m_value)
    {
        that.m_value = -1;
    }

    moveable_resource& operator= (moveable_resource&& that) noexcept
    {
        m_value = that.m_value;
        that.m_value = -1;
        return *this;
    }

    moveable_resource(moveable_resource const&) = delete;
    moveable_resource& operator= (moveable_resource const&) = delete;

    int get() const noexcept
    {
        return m_value;
    }

    bool operator== (moveable_resource const& that) const noexcept
    {
        return m_value == that.m_value;
    }

    bool operator!= (moveable_resource const& that) const noexcept
    {
        return !operator==(that);
    }

    friend std::ostream& operator<< (std::ostream& strm, moveable_resource const& res)
    {
        strm << "{ " << res.m_value << " }";
        return strm;
    }

    friend void copy_resource(moveable_resource const& from, moveable_resource& to)
    {
        to.m_value = from.m_value;
    }
};

class copyable_resource
{
private:
    int m_value;

public:
    explicit copyable_resource(int value = 0) noexcept :
        m_value(value)
    {
    }

    copyable_resource(copyable_resource const& that) noexcept :
        m_value(that.m_value)
    {
    }

    copyable_resource& operator= (copyable_resource const& that) noexcept
    {
        m_value = that.m_value;
        return *this;
    }

    int get() const noexcept
    {
        return m_value;
    }

    bool operator== (copyable_resource const& that) const noexcept
    {
        return m_value == that.m_value;
    }

    bool operator!= (copyable_resource const& that) const noexcept
    {
        return !operator==(that);
    }

    friend std::ostream& operator<< (std::ostream& strm, copyable_resource const& res)
    {
        strm << "{ " << res.m_value << " }";
        return strm;
    }
};

template< typename Resource >
class throwing_resource_deleter :
    public checking_resource_deleter< Resource >
{
private:
    using base_type = checking_resource_deleter< Resource >;

private:
    mutable bool m_throw;

public:
    explicit throwing_resource_deleter(int& n, bool do_throw = false) noexcept :
        base_type(n),
        m_throw(do_throw)
    {
    }

    explicit throwing_resource_deleter(Resource& deleted, int& n, bool do_throw = false) noexcept :
        base_type(deleted, n),
        m_throw(do_throw)
    {
    }

    throwing_resource_deleter(throwing_resource_deleter const& that) :
        base_type(static_cast< base_type const& >(that)),
        m_throw(that.m_throw)
    {
        if (m_throw)
            throw std::runtime_error("throwing_resource_deleter copy ctor");
    }

    throwing_resource_deleter& operator=(throwing_resource_deleter const& that)
    {
        *static_cast< base_type* >(this) = static_cast< base_type const& >(that);
        m_throw = that.m_throw;
        if (m_throw)
            throw std::runtime_error("throwing_resource_deleter copy assignment");
        return *this;
    }

    void set_throw(bool do_throw) const noexcept
    {
        m_throw = do_throw;
    }
};

template< typename Resource >
using default_resource_traits = void;

template< typename Resource >
struct wrapped_int_resource_traits
{
    static Resource make_default() noexcept
    {
        return Resource(-1);
    }

    static bool is_allocated(Resource const& res) noexcept
    {
        return res.get() >= 0;
    }
};

template< template< typename > class Traits >
void check_throw_deleter()
{
    int n = 0;
    {
        try
        {
            using unique_resource_t = boost::scope::unique_resource< copyable_resource, throwing_resource_deleter< copyable_resource >, Traits< copyable_resource > >;
            unique_resource_t ur1{ boost::scope::default_resource, throwing_resource_deleter< copyable_resource >(n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                BOOST_TEST_EQ(n, 0);
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 0);
    }

    n = 0;
    {
        copyable_resource deleted_res1;
        try
        {
            using unique_resource_t = boost::scope::unique_resource< copyable_resource, throwing_resource_deleter< copyable_resource >, Traits< copyable_resource > >;
            unique_resource_t ur1{ copyable_resource{ 10 }, throwing_resource_deleter< copyable_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                BOOST_TEST_EQ(n, 0);
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, copyable_resource{ 10 });
    }

    n = 0;
    {
        copyable_resource deleted_res1, deleted_res2;
        try
        {
            using unique_resource_t = boost::scope::unique_resource< copyable_resource, throwing_resource_deleter< copyable_resource >, Traits< copyable_resource > >;
            unique_resource_t ur1{ copyable_resource{ 10 }, throwing_resource_deleter< copyable_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2{ copyable_resource{ 20 }, throwing_resource_deleter< copyable_resource >(deleted_res2, n) };
                ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                BOOST_TEST_EQ(n, 1);
                BOOST_TEST_EQ(deleted_res2, copyable_resource{ 20 });
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 2);
        BOOST_TEST_EQ(deleted_res1, copyable_resource{ 10 });
    }

    n = 0;
    {
        try
        {
            using unique_resource_t = boost::scope::unique_resource< moveable_resource, throwing_resource_deleter< moveable_resource >, Traits< moveable_resource > >;
            unique_resource_t ur1{ boost::scope::default_resource, throwing_resource_deleter< moveable_resource >(n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                BOOST_TEST_EQ(n, 0);
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 0);
    }

    n = 0;
    {
        moveable_resource deleted_res1;
        try
        {
            using unique_resource_t = boost::scope::unique_resource< moveable_resource, throwing_resource_deleter< moveable_resource >, Traits< moveable_resource > >;
            unique_resource_t ur1{ moveable_resource{ 10 }, throwing_resource_deleter< moveable_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                // The resource was moved from ur1, but the deleter could not have been moved. Then the resource was moved back to ur1.
                BOOST_TEST_EQ(n, 0);
                BOOST_TEST(ur1.allocated());
                BOOST_TEST_EQ(ur1.get(), moveable_resource{ 10 });
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, moveable_resource{ 10 });
    }

    n = 0;
    {
        moveable_resource deleted_res1, deleted_res2;
        try
        {
            using unique_resource_t = boost::scope::unique_resource< moveable_resource, throwing_resource_deleter< moveable_resource >, Traits< moveable_resource > >;
            unique_resource_t ur1{ moveable_resource{ 10 }, throwing_resource_deleter< moveable_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2{ moveable_resource{ 20 }, throwing_resource_deleter< moveable_resource >(deleted_res2, n) };
                ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                // First, ur2 was reset - the deleter was called on the resource 20. Then the deleter could not have been moved from ur1,
                // leaving ur1 intact.
                BOOST_TEST_EQ(n, 1);
                BOOST_TEST_EQ(deleted_res2, moveable_resource{ 20 });
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 2);
        BOOST_TEST_EQ(deleted_res1, moveable_resource{ 10 });
        BOOST_TEST_EQ(deleted_res2, moveable_resource{ 20 });
    }
}

// Specially-crafted resource type that is:
// * nothrow move-constructible
// * NOT-nothrow move-assignable
// * has data layout which favors placing the "allocated" flag in unique_resource in its tail padding
class move_constructible_resource
{
public:
    // A special tag to construct "default" resource value
    struct default_tag { };

private:
    int m_n1;
    signed char m_n2;

public:
    constexpr move_constructible_resource() noexcept : m_n1(0), m_n2(0) { }

    // For compatibility with move_constructible_resource_traits::make_default()
    explicit constexpr move_constructible_resource(default_tag) noexcept : move_constructible_resource() { }

    explicit move_constructible_resource(int n1, signed char n2 = 0) noexcept : m_n1(n1), m_n2(n2) { }

    move_constructible_resource(move_constructible_resource&& that) noexcept : m_n1(that.m_n1), m_n2(that.m_n2)
    {
        that.m_n1 = 0;
        that.m_n2 = 0;
    }

    move_constructible_resource(move_constructible_resource const& that) : m_n1(that.m_n1), m_n2(that.m_n2)
    {
    }

    move_constructible_resource& operator= (move_constructible_resource&& that) // not noexcept
    {
        m_n1 = that.m_n1;
        m_n2 = that.m_n2;
        that.m_n1 = 0;
        that.m_n2 = 0;
        return *this;
    }

    move_constructible_resource& operator= (move_constructible_resource const& that)
    {
        m_n1 = that.m_n1;
        m_n2 = that.m_n2;
        return *this;
    }

    // For compatibility with move_constructible_resource_traits::make_default()
    move_constructible_resource& operator= (default_tag) noexcept
    {
        m_n1 = 0;
        m_n2 = 0;
        return *this;
    }

    bool operator== (move_constructible_resource const& that) const noexcept
    {
        return m_n1 == that.m_n1 && m_n2 == that.m_n2;
    }

    bool operator!= (move_constructible_resource const& that) const noexcept
    {
        return !operator==(that);
    }

    friend std::ostream& operator<< (std::ostream& strm, move_constructible_resource const& res)
    {
        strm << "{ " << res.m_n1 << ", " << static_cast< int >(res.m_n2) << " }";
        return strm;
    }

    friend void copy_resource(move_constructible_resource const& from, move_constructible_resource& to)
    {
        to.m_n1 = from.m_n1;
        to.m_n2 = from.m_n2;
    }
};

//! Resource traits for \c move_constructible_resource
struct move_constructible_resource_traits
{
    static move_constructible_resource::default_tag make_default() noexcept { return move_constructible_resource::default_tag{}; }
    static bool is_allocated(move_constructible_resource const& res) noexcept { return res != move_constructible_resource{}; }
};

void check_throw_deleter_move_constructible_resource()
{
    int n = 0;
    {
        move_constructible_resource deleted_res1;
        try
        {
            using unique_resource_t = boost::scope::unique_resource< move_constructible_resource, throwing_resource_deleter< move_constructible_resource >, move_constructible_resource_traits >;
            unique_resource_t ur1{ move_constructible_resource(10, 5), throwing_resource_deleter< move_constructible_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                // The resource was moved from ur1, but the deleter could not have been moved. Then the resource was moved back to ur1.
                BOOST_TEST_EQ(n, 0);
                BOOST_TEST(ur1.allocated());
                BOOST_TEST_EQ(ur1.get(), move_constructible_resource(10, 5));
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, move_constructible_resource(10, 5));
    }

    n = 0;
    {
        move_constructible_resource deleted_res1;
        try
        {
            // No resource traits to force using the "allocated" flag
            using unique_resource_t = boost::scope::unique_resource< move_constructible_resource, throwing_resource_deleter< move_constructible_resource > >;
            unique_resource_t ur1{ move_constructible_resource(10, 0), throwing_resource_deleter< move_constructible_resource >(deleted_res1, n) };
            ur1.get_deleter().set_throw(true);
            try
            {
                unique_resource_t ur2 = std::move(ur1);
                BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
            }
            catch (...)
            {
                // The resource was moved from ur1, but the deleter could not have been moved. Then the resource was moved back to ur1.
                BOOST_TEST_EQ(n, 0);
                BOOST_TEST(ur1.allocated());
                BOOST_TEST_EQ(ur1.get(), move_constructible_resource(10, 0));
                throw;
            }
        }
        catch (...)
        {
        }
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, move_constructible_resource(10, 0));
    }
}


void check_deduction()
{
    struct local
    {
        static void raw_func_deleter(int)
        {
            ++g_n;
        }

#if !defined(BOOST_SCOPE_NO_CXX17_NOEXCEPT_FUNCTION_TYPES)
        static void raw_func_deleter_noexcept(int) noexcept
        {
            ++g_n;
        }
#endif
    };

#if !defined(BOOST_NO_CXX17_DEDUCTION_GUIDES)
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, empty_resource_deleter< int > >;
        boost::scope::unique_resource ur{ 0, empty_resource_deleter< int >() };
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
    {
        using expected_unique_resource_t = boost::scope::unique_resource< struct_resource, empty_resource_deleter< struct_resource > >;
        boost::scope::unique_resource ur{ struct_resource(), empty_resource_deleter< struct_resource >() };
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, void(*)(int) >;
        boost::scope::unique_resource ur{ 0, local::raw_func_deleter };
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
#if !defined(BOOST_SCOPE_NO_CXX17_NOEXCEPT_FUNCTION_TYPES)
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, void(*)(int) noexcept >;
        boost::scope::unique_resource ur{ 0, local::raw_func_deleter_noexcept };
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
#endif
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, empty_resource_deleter< int > >;
        boost::scope::unique_resource ur1{ 0, empty_resource_deleter< int >() };
        BOOST_TEST_TRAIT_SAME(decltype(ur1), expected_unique_resource_t);
        boost::scope::unique_resource ur2 = std::move(ur1);
        BOOST_TEST_TRAIT_SAME(decltype(ur2), expected_unique_resource_t);
    }
#endif // !defined(BOOST_NO_CXX17_DEDUCTION_GUIDES)

    int n = 0, deleted_res = -1;
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, checking_resource_deleter< int > >;
        auto ur = boost::scope::make_unique_resource_checked(10, 0, checking_resource_deleter< int >(deleted_res, n));
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res, 10);

    n = 0;
    deleted_res = -1;
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, checking_resource_deleter< int > >;
        auto ur = boost::scope::make_unique_resource_checked(0, 0, checking_resource_deleter< int >(deleted_res, n));
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
        BOOST_TEST_EQ(ur.get(), 0);
        BOOST_TEST(!ur.allocated());
    }
    BOOST_TEST_EQ(n, 0);
    BOOST_TEST_EQ(deleted_res, -1);

    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, void(*)(int) >;
        auto ur = boost::scope::make_unique_resource_checked(0, 0, local::raw_func_deleter);
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
#if !defined(BOOST_SCOPE_NO_CXX17_NOEXCEPT_FUNCTION_TYPES)
    {
        using expected_unique_resource_t = boost::scope::unique_resource< int, void(*)(int) noexcept >;
        auto ur = boost::scope::make_unique_resource_checked(0, 0, local::raw_func_deleter_noexcept);
        BOOST_TEST_TRAIT_SAME(decltype(ur), expected_unique_resource_t);
    }
#endif

    n = 0;
    try
    {
        // Not required to throw, but either way the deleter should not be called
        auto ur = boost::scope::make_unique_resource_checked(throw_on_copy_resource{ 0 }, throw_on_copy_resource{ 0 }, checking_resource_deleter< throw_on_copy_resource >(n));
    }
    catch (...)
    {
    }
    BOOST_TEST_EQ(n, 0);

    n = 0;
    try
    {
        auto ur = boost::scope::make_unique_resource_checked(0, 0, throwing_resource_deleter< int >(n, true));
        BOOST_ERROR("An exception is expected to be thrown by throwing_resource_deleter");
    }
    catch (...)
    {
    }
    BOOST_TEST_EQ(n, 0);
}

struct int_resource_traits
{
    static int make_default() noexcept
    {
        return -1;
    }

    static bool is_allocated(int res) noexcept
    {
        return res >= 0;
    }
};

void check_resource_traits()
{
    {
        boost::scope::unique_resource< int, empty_resource_deleter< int >, int_resource_traits > ur;
        BOOST_TEST_EQ(ur.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur.allocated());
    }

    int n = 0, deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur{ -10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), -10);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }
    BOOST_TEST_EQ(n, 0);
    BOOST_TEST_EQ(deleted_res1, -1);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur{ 0, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 0);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!!ur);
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, 0);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        ur.release();
        BOOST_TEST_EQ(ur.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur.allocated());
    }
    BOOST_TEST_EQ(n, 0);
    BOOST_TEST_EQ(deleted_res1, -1);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        ur.reset();
        BOOST_TEST(!ur.allocated());
        BOOST_TEST_EQ(ur.get(), int_resource_traits::make_default());
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, 10);
    }
    BOOST_TEST_EQ(n, 1);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        ur.reset(20);
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, 10);
        deleted_res1 = -1;
        BOOST_TEST_EQ(ur.get(), 20);
        BOOST_TEST(ur.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 20);

    n = 0;
    deleted_res1 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur2{ std::move(ur1) };
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST_EQ(ur1.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur1.allocated());
    }
    BOOST_TEST_EQ(n, 1);
    BOOST_TEST_EQ(deleted_res1, 10);

    n = 0;
    deleted_res1 = -1;
    int deleted_res2 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur2{ 20, checking_resource_deleter< int >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST(ur2.allocated());
        ur2 = std::move(ur1);
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        BOOST_TEST_EQ(ur1.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur1.allocated());
        BOOST_TEST_EQ(n, 1);
        BOOST_TEST_EQ(deleted_res1, -1);
        BOOST_TEST_EQ(deleted_res2, 20);
        deleted_res2 = -1;
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 10);
    BOOST_TEST_EQ(deleted_res2, -1);

    {
        boost::scope::unique_resource< int, empty_resource_deleter< int >, int_resource_traits > ur1;
        BOOST_TEST_EQ(ur1.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur1.allocated());
        boost::scope::unique_resource< int, empty_resource_deleter< int >, int_resource_traits > ur2{ 10, empty_resource_deleter< int >() };
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), int_resource_traits::make_default());
        BOOST_TEST(!ur2.allocated());
    }

    n = 0;
    deleted_res1 = -1;
    deleted_res2 = -1;
    {
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur1{ 10, checking_resource_deleter< int >(deleted_res1, n) };
        BOOST_TEST_EQ(ur1.get(), 10);
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur1.allocated());
        boost::scope::unique_resource< int, checking_resource_deleter< int >, int_resource_traits > ur2{ 20, checking_resource_deleter< int >(deleted_res2, n) };
        BOOST_TEST_EQ(ur2.get(), 20);
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur2.allocated());
        using namespace std;
        swap(ur1, ur2);
        BOOST_TEST_EQ(n, 0);
        BOOST_TEST_EQ(ur1.get(), 20);
        BOOST_TEST_EQ(ur1.get_deleter().get_deleted(), &deleted_res2);
        BOOST_TEST(ur1.allocated());
        BOOST_TEST_EQ(ur2.get(), 10);
        BOOST_TEST_EQ(ur2.get_deleter().get_deleted(), &deleted_res1);
        BOOST_TEST(ur2.allocated());
    }
    BOOST_TEST_EQ(n, 2);
    BOOST_TEST_EQ(deleted_res1, 10);
    BOOST_TEST_EQ(deleted_res2, 20);
}

#if !defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)

struct global_deleter_int
{
    void operator() (int) noexcept
    {
        ++g_n;
    }
};

struct global_deleter_ptr
{
    void operator() (int*) noexcept
    {
        ++g_n;
    }
};

void check_simple_resource_traits()
{
    g_n = 0;
    {
        boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur;
        BOOST_TEST_EQ(ur.get(), -1);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }
    BOOST_TEST_EQ(g_n, 0);

    g_n = 0;
    {
        boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur{ -1 };
        BOOST_TEST_EQ(ur.get(), -1);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }
    BOOST_TEST_EQ(g_n, 0);

    g_n = 0;
    {
        boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur{ 10 };
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!!ur);
    }
    BOOST_TEST_EQ(g_n, 1);

    g_n = 0;
    {
        boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1, -2, -3 > > ur;
        BOOST_TEST_EQ(ur.get(), -1);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);

        ur.reset(-2);
        BOOST_TEST_EQ(g_n, 0);
        BOOST_TEST_EQ(ur.get(), -2);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);

        ur.reset(-3);
        BOOST_TEST_EQ(g_n, 0);
        BOOST_TEST_EQ(ur.get(), -3);
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);

        ur.reset(10);
        BOOST_TEST_EQ(g_n, 0);
        BOOST_TEST_EQ(ur.get(), 10);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!!ur);
    }
    BOOST_TEST_EQ(g_n, 1);

    g_n = 0;
    {
        boost::scope::unique_resource< int*, global_deleter_ptr, boost::scope::unallocated_resource< nullptr > > ur;
        BOOST_TEST_EQ(ur.get(), static_cast< int* >(nullptr));
        BOOST_TEST(!ur.allocated());
        BOOST_TEST(!ur);
    }
    BOOST_TEST_EQ(g_n, 0);

    g_n = 0;
    {
#if !defined(BOOST_MSVC) || (BOOST_MSVC >= 1920)
        boost::scope::unique_resource< int*, global_deleter_ptr, boost::scope::unallocated_resource< nullptr > > ur{ &g_n };
#else
        // MSVC 14.1 fails with ICE on check_simple_resource_traits. It's not clear what exactly upsets the compiler,
        // but fiddling with this constructor call seems to allow the compilation to succeed.
        // https://developercommunity.visualstudio.com/t/ICE-when-compiling-NTTP-related-BoostSc/10922599
        boost::scope::unique_resource< int*, global_deleter_ptr, boost::scope::unallocated_resource< nullptr > > ur{ &g_n, global_deleter_ptr() };
#endif
        BOOST_TEST_EQ(ur.get(), &g_n);
        BOOST_TEST(ur.allocated());
        BOOST_TEST(!!ur);
    }
    BOOST_TEST_EQ(g_n, 1);
}

#endif // !defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)

int main()
{
    check_int();
    check_struct();
    check_ptr();
    check_ref();
    check_throw_resource();
    check_throw_deleter< default_resource_traits >();
    check_throw_deleter< wrapped_int_resource_traits >();
    check_throw_deleter_move_constructible_resource();
    check_deduction();
    check_resource_traits();
#if !defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)
    check_simple_resource_traits();
#endif
    return boost::report_errors();
}
