// Copyright (C) 2022 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #include "tester.h" #include "../any/storage.h" #include "../any/any_function.h" namespace { using namespace test; using namespace dlib; using namespace te; logger dlog("test.te"); struct A { A( int& copy_counter_, int& move_counter_, int& delete_counter_ ) : copy_counter{copy_counter_}, move_counter{move_counter_}, delete_counter{delete_counter_} {} A(const A& other) : copy_counter{other.copy_counter}, move_counter{other.move_counter}, delete_counter{other.delete_counter} { ++copy_counter; } A(A&& other) : copy_counter{other.copy_counter}, move_counter{other.move_counter}, delete_counter{other.delete_counter} { ++move_counter; } ~A() { ++delete_counter; } int& copy_counter; int& move_counter; int& delete_counter; }; template <typename Storage> void test_storage_basic() { Storage a; DLIB_TEST(a.get_ptr() == nullptr); DLIB_TEST(a.is_empty()); DLIB_TEST(a.template contains<int>() == false); int value = 5; a = value; DLIB_TEST(a.get_ptr() != nullptr); DLIB_TEST(!a.is_empty()); DLIB_TEST(a.template contains<int>()); DLIB_TEST(!a.template contains<std::string>()); DLIB_TEST(a.template cast_to<int>() == 5); DLIB_TEST(a.template get<int>() == 5); Storage b = a; DLIB_TEST(b.get_ptr() != nullptr); DLIB_TEST(!b.is_empty()); DLIB_TEST(b.template contains<int>()); DLIB_TEST(b.template cast_to<int>() == 5); DLIB_TEST(b.template get<int>() == 5); DLIB_TEST(a.get_ptr() != nullptr); DLIB_TEST(!a.is_empty()); DLIB_TEST(a.template contains<int>()); DLIB_TEST(a.template cast_to<int>() == 5); DLIB_TEST(a.template get<int>() == 5); DLIB_TEST(*static_cast<int*>(a.get_ptr()) == 5); DLIB_TEST(*static_cast<int*>(b.get_ptr()) == 5); a.clear(); DLIB_TEST(a.get_ptr() == nullptr); DLIB_TEST(a.is_empty()); DLIB_TEST(!a.template contains<int>()); } void test_type_erasure() { int copy_counter = 0; int move_counter = 0; int delete_counter = 0; { storage_heap str1{A{copy_counter, move_counter, delete_counter}}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_heap str2 = str1; DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_heap str3 = std::move(str2); DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); //pointer was moved with storage_heap so move constructor not called DLIB_TEST(delete_counter == 1); DLIB_TEST(str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(!str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 3); //one of the pointers was moved so one of the destructors was not called copy_counter = move_counter = delete_counter = 0; { storage_stack<sizeof(A)> str1{A{copy_counter, move_counter, delete_counter}}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_stack<sizeof(A)> str2 = str1; DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_stack<sizeof(A)> str3 = std::move(str2); DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 2); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 2); DLIB_TEST(delete_counter == 4); copy_counter = move_counter = delete_counter = 0; { storage_sbo<4> str1{A{copy_counter, move_counter, delete_counter}}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_sbo<4> str2 = str1; DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_sbo<4> str3 = std::move(str2); DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); // SBO 4 isn't big enough, so heap is used, so pointers are moved DLIB_TEST(delete_counter == 1); DLIB_TEST(str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(!str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 3); //one of the pointers was moved so one of the destructors was not called copy_counter = move_counter = delete_counter = 0; { storage_sbo<sizeof(A)> str1{A{copy_counter, move_counter, delete_counter}}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_sbo<sizeof(A)> str2 = str1; DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_sbo<sizeof(A)> str3 = std::move(str2); DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 2); // SBO is big enough, so stack is used, so move constructor is used DLIB_TEST(delete_counter == 1); DLIB_TEST(!str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 1); DLIB_TEST(move_counter == 2); DLIB_TEST(delete_counter == 4); copy_counter = move_counter = delete_counter = 0; { storage_shared str1{A{copy_counter, move_counter, delete_counter}}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_shared str2 = str1; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_shared str3 = std::move(str2); DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 1); DLIB_TEST(str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(!str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 1); DLIB_TEST(delete_counter == 2); copy_counter = move_counter = delete_counter = 0; { A a{copy_counter, move_counter, delete_counter}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 0); DLIB_TEST(delete_counter == 0); storage_view str1{a}; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 0); DLIB_TEST(delete_counter == 0); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); storage_view str2 = str1; DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 0); DLIB_TEST(delete_counter == 0); DLIB_TEST(!str1.contains<int>()); DLIB_TEST(str1.contains<A>()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str2.is_empty()); storage_view str3 = std::move(str2); DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 0); DLIB_TEST(delete_counter == 0); DLIB_TEST(!str2.is_empty()); DLIB_TEST(!str2.contains<int>()); DLIB_TEST(str2.contains<A>()); DLIB_TEST(!str3.contains<int>()); DLIB_TEST(str3.contains<A>()); } DLIB_TEST(copy_counter == 0); DLIB_TEST(move_counter == 0); DLIB_TEST(delete_counter == 1); } template<typename Function> void test_function() { Function f1; DLIB_TEST(!f1); int a = 42; Function f2{[a](int b) {return a + b;}}; DLIB_TEST(f2); DLIB_TEST(f2(1) == 43); Function f3{f2}; DLIB_TEST(f2); DLIB_TEST(f3); DLIB_TEST(f2(2) == 44); DLIB_TEST(f3(2) == 44); f1 = f2; DLIB_TEST(f1); DLIB_TEST(f2); DLIB_TEST(f3); DLIB_TEST(f1(2) == 44); DLIB_TEST(f2(2) == 44); DLIB_TEST(f3(2) == 44); Function f4{std::move(f2)}; DLIB_TEST(f1); DLIB_TEST(!f2); DLIB_TEST(f3); DLIB_TEST(f4); DLIB_TEST(f1(2) == 44); DLIB_TEST(f3(2) == 44); DLIB_TEST(f4(2) == 44); f2 = std::move(f1); DLIB_TEST(!f1); DLIB_TEST(f2); DLIB_TEST(f3); DLIB_TEST(f4); DLIB_TEST(f2(2) == 44); DLIB_TEST(f3(2) == 44); DLIB_TEST(f4(2) == 44); } void test_function_view() { auto f = [a = 42](int i) mutable {a += i; return a;}; dlib::any_function_view<int(int)> g(f); DLIB_TEST(f(1) == 43); DLIB_TEST(g(1) == 44); DLIB_TEST(f(1) == 45); DLIB_TEST(g(1) == 46); } void global_function1(int& a) {a += 1;} template<template<class...> class Function, class Storage> void test_function_pointer() { { Function<Storage, void(int&)> f{global_function1}; DLIB_TEST(f); int a = 0; f(a); DLIB_TEST(a == 1); f = nullptr; DLIB_TEST(!f); f = global_function1; DLIB_TEST(f); f(a); DLIB_TEST(a == 2); } /*! Use address !*/ { Function<Storage, void(int&)> f{&global_function1}; DLIB_TEST(f); int a = 0; f(a); DLIB_TEST(a == 1); f = nullptr; DLIB_TEST(!f); f = &global_function1; DLIB_TEST(f); f(a); DLIB_TEST(a == 2); } } struct member_function { void increment(int& a) const {a += 1;} }; template<template<class...> class Function, class Storage> void test_member_pointer() { { member_function obj; Function<Storage, void(member_function&, int&)> f{&member_function::increment}; DLIB_TEST(f); int a = 0; f(obj, a); DLIB_TEST(a == 1); f = nullptr; DLIB_TEST(!f); f = &member_function::increment; DLIB_TEST(f); f(obj, a); DLIB_TEST(a == 2); } /*! Use std::mem_fn !*/ { member_function obj; Function<Storage, void(member_function&, int&)> f{std::mem_fn(&member_function::increment)}; DLIB_TEST(f); int a = 0; f(obj, a); DLIB_TEST(a == 1); f = nullptr; DLIB_TEST(!f); f = std::mem_fn(&member_function::increment); DLIB_TEST(f); f(obj, a); DLIB_TEST(a == 2); } /*! Use std::bind !*/ { using namespace std::placeholders; member_function obj; Function<Storage, void(int&)> f{std::bind(&member_function::increment, obj, _1)}; DLIB_TEST(f); int a = 0; f(a); DLIB_TEST(a == 1); f = nullptr; DLIB_TEST(!f); f = std::bind(&member_function::increment, obj, _1); DLIB_TEST(f); f(a); DLIB_TEST(a == 2); } } class te_tester : public tester { public: te_tester ( ) : tester ("test_te", "Runs tests on type erasure tools") {} void perform_test () { test_type_erasure(); dlog << LINFO << "test_storage_basic<storage_heap>()"; test_storage_basic<storage_heap>(); dlog << LINFO << "test_storage_basic<storage_sbo<20>>()"; test_storage_basic<storage_sbo<20>>(); dlog << LINFO << "test_storage_basic<storage_stack<20>>()"; test_storage_basic<storage_stack<20>>(); dlog << LINFO << "test_storage_basic<storage_shared>()"; test_storage_basic<storage_shared>(); dlog << LINFO << "test_storage_basic<storage_view>()"; test_storage_basic<storage_view>(); test_function<dlib::any_function<int(int)>>(); test_function_view(); test_function_pointer<dlib::any_function_basic, storage_heap>(); test_function_pointer<dlib::any_function_basic, storage_stack<32>>(); test_function_pointer<dlib::any_function_basic, storage_sbo<32>>(); test_function_pointer<dlib::any_function_basic, storage_shared>(); test_member_pointer<dlib::any_function_basic, storage_heap>(); test_member_pointer<dlib::any_function_basic, storage_stack<32>>(); test_member_pointer<dlib::any_function_basic, storage_sbo<32>>(); test_member_pointer<dlib::any_function_basic, storage_shared>(); } } a; }