Random Useful Stuff Thread

Pages: 12
I figured it could be useful to have some random snippets of code that might be useful here.

If you participate, make sure to post _two_ snippets:

  • one appropriate for cut-n-paste into a header file, and
  • another appropriate for cut-n-pas into an implementation file.

No fancy stuff like #include guards and the like. Assume your readers know how to frob a text editor sufficient to compile stuff.


BY CATEGORY
──────────────────────────────────────────────────────────────────────
algorithm
find_first_true (binary search) • http://cplusplus.com/forum/lounge/279954/#msg1210356
lvalue (from rvalue) • http://cplusplus.com/forum/lounge/279954/2/#msg1211402
RangeF (subrange in range) • http://cplusplus.com/forum/lounge/279954/#msg1210389
ranger, re_ranger (iterators --> ranges) • http://cplusplus.com/forum/lounge/279954/#msg1211401

input|output
Extract unsigned integers from byte stream with known endianness • http://cplusplus.com/forum/lounge/279954/2/#msg1211823
getInp (std::cin/cout helper) • http://cplusplus.com/forum/lounge/279954/#msg1210390
set000s, comma_facet (numpunct<> example) • http://cplusplus.com/forum/lounge/279954/#msg1210508
Simple Menu Toolkit • http://cplusplus.com/forum/lounge/279954/#msg1210396

language|class|type
Cast enum class element to underlying type value • http://cplusplus.com/forum/lounge/279954/2/#msg1211823

random number generation
Simple Random Number Generation Toolkit • http://cplusplus.com/forum/lounge/279954/#msg1210399

string
ci_compare (case_insensitive Unicode string compare) • http://cplusplus.com/forum/lounge/279954/#msg1210095
format_size (Human readable integers) • http://cplusplus.com/forum/lounge/279954/#msg1210356
StringCast (Windows native UTF_8 ←→ UTF_16) • http://cplusplus.com/forum/lounge/279954/#msg1210351

thread|process
IsConsole (is process attached to a console?) • http://cplusplus.com/forum/lounge/279954/#msg1210353
LoadFunction<> (LoadLibrary & GetProcAddress helper) • http://cplusplus.com/forum/lounge/279954/#msg1210354
LOCK_MUTEX (std::lock_guard helper macro) • http://cplusplus.com/forum/lounge/279954/#msg1210742

windows console
clear_screen • http://cplusplus.com/forum/lounge/279954/#msg1210394
ProgramPauser • http://cplusplus.com/forum/lounge/279954/#msg1210395


BY USERNAME
──────────────────────────────────────────────────────────────────────
Furry Guy
clear_screen • http://cplusplus.com/forum/lounge/279954/#msg1210394
ProgramPauser • http://cplusplus.com/forum/lounge/279954/#msg1210395
Simple Menu Toolkit • http://cplusplus.com/forum/lounge/279954/#msg1210396
Simple Random Number Generation Toolkit • http://cplusplus.com/forum/lounge/279954/#msg1210399

Dúthomhas
ci_compare (case_insensitive Unicode string compare) • http://cplusplus.com/forum/lounge/279954/#msg1210095
lvalue (from rvalue) • http://cplusplus.com/forum/lounge/279954/2/#msg1211402
ranger, re_ranger (iterators --> ranges) • http://cplusplus.com/forum/lounge/279954/#msg1211401

helios
find_first_true (binary search) • http://cplusplus.com/forum/lounge/279954/#msg1210356
format_size (Human_readable integers) • http://cplusplus.com/forum/lounge/279954/#msg1210356
LOCK_MUTEX (std::lock_guard helper macro) • http://cplusplus.com/forum/lounge/279954/#msg1210742

malibor
IsConsole (is process attached to a console?) • http://cplusplus.com/forum/lounge/279954/#msg1210353
LoadFunction<> (LoadLibrary & GetProcAddress helper) • http://cplusplus.com/forum/lounge/279954/#msg1210354
StringCast (Windows native UTF_8 ←→ UTF_16) • http://cplusplus.com/forum/lounge/279954/#msg1210351

mbozzi
Cast enum class element to underlying type value • http://cplusplus.com/forum/lounge/279954/2/#msg1211823
Extract unsigned integers from byte stream with known endianness • http://cplusplus.com/forum/lounge/279954/2/#msg1211823

seeplus
RangeF (subrange in range) • http://cplusplus.com/forum/lounge/279954/#msg1210389
getInp (std::cin/cout helper) • http://cplusplus.com/forum/lounge/279954/#msg1210390
set000s, comma_facet (numpunct<> example) • http://cplusplus.com/forum/lounge/279954/#msg1210508
Last edited on
Case-insensitive Unicode string comparison. Works most of the time. Errs on fail mode.

(Won't work with the Turkish "I" without special frobbing, but I don't care.)

1
2
3
4
#include <string>

bool ci_compare( const std::u16string& a, const std::u16string& b );  // UTF-16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifdef _WIN32
  #include <windows.h>
  #include <string>

  bool ci_compare( const std::u16string& a, const std::u16string& b )
  {
    return CompareStringEx(
      NULL, // Unicode
      NORM_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH,
      (LPCWCH)a.c_str(), a.size(),
      (LPCWCH)b.c_str(), b.size(),
      NULL, NULL, 0 )
    == CSTR_EQUAL;
  }

#else
  #include <unicode/unistr.h>

  bool ci_compare( const std::u16string& a, const std::u16string& b )
  {
    return icu::UnicodeString( a.c_str() ) .caseCompare( icu::UnicodeString( b.c_str() ), 0 ) == 0;
  }

#endif 

Conversion between UTF-8 and UTF-16 is a different matter, so, not included here. If you really need something the std::filesystem::path object can be co-opted to perform those conversions for you in a pinch.


Compile on Windows as-is.
Compile on Linux / OS X with `pkg-config --libs icu-uc` for the link options.

Every major (and many minor) platform has ICU on it somewhere. Windows keeps its own version for its own personal use, so we don't bother trying to directly link to it here — it gets used in the background anyway.
Last edited on
Hi Duthomhas, nice thread start!

how about native string conversion? here is my implementation that works...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <string>
#include <Windows.h>

std::string StringCast(const std::wstring& param, UINT code_page = CP_UTF8)
{
	if (param.empty())
		return std::string();

	DWORD flags = WC_ERR_INVALID_CHARS;
	//flags |= WC_COMPOSITECHECK;
	flags |= WC_NO_BEST_FIT_CHARS;

	switch (code_page)
	{
	case 50220:
	case 50221:
	case 50222:
	case 50225:
	case 50227:
	case 50229:
	case 65000:
	case 42:
		flags = 0;
		break;
	case 54936:
	case CP_UTF8:
		flags = WC_ERR_INVALID_CHARS; // or 0
		break;
	default:
		if ((code_page >= 57002) && (code_page <= 57011))
			flags = 0;
		break;
	}

	const int source_wchar_size = static_cast<int>(param.size());
	const int char_size_needed = WideCharToMultiByte(code_page, flags, param.c_str(), source_wchar_size, nullptr, 0, nullptr, nullptr);

	if (char_size_needed == 0)
	{
		ShowError(ERROR_INFO);
		return std::string();
	}

	std::string return_string(static_cast<const unsigned int>(char_size_needed), 0);

	if (!WideCharToMultiByte(code_page, flags, param.c_str(), source_wchar_size, &return_string[0], char_size_needed, nullptr, nullptr))
	{
		ShowError(ERROR_INFO);
		return std::string();
	}

	return return_string;
}


Where ShowError(ERROR_INFO) is an optional macro function that internally formats the conversion error known as GetLastError() and shows the formatted error message to the screen if any.

here is the opposite, from string to wstring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
std::wstring StringCast(const std::string& param, UINT code_page = CP_UTF8)
{
	if (param.empty())
		return std::wstring();

	// TODO: flags for string conversion
	DWORD flags = MB_ERR_INVALID_CHARS;
	//flags |= MB_USEGLYPHCHARS;
	//flags |= MB_PRECOMPOSED;

	switch (code_page)
	{
	case 50220:
	case 50221:
	case 50222:
	case 50225:
	case 50227:
	case 50229:
	case 65000:
	case 42:
		flags = 0;
		break;
	case 54936:
	case CP_UTF8:
		flags = MB_ERR_INVALID_CHARS; // or 0
		break;
	default:
		if ((code_page >= 57002) && (code_page <= 57011))
			flags = 0;
		break;
	}

	const int source_char_size = static_cast<int>(param.size());
	const int wchar_size_needed = MultiByteToWideChar(code_page, flags, param.c_str(), source_char_size, nullptr, 0);

	if (wchar_size_needed == 0)
	{
		ShowError(ERROR_INFO);
		return std::wstring();
	}

	std::wstring return_string(static_cast<const unsigned int>(wchar_size_needed), 0);

	if (!MultiByteToWideChar(code_page, flags, param.c_str(), source_char_size, &return_string[0], wchar_size_needed))
	{
		ShowError(ERROR_INFO);
		return std::wstring();
	}

	return return_string;
}

Last edited on
A function that helps you to determine if current program is console or windowed program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <array>
#include <Windows.h>

bool IsConsole() noexcept
{
	std::array<DWORD, 1> processes;

	// MSDN: Retrieves a list of the processes attached to the current console.
	// if it's windows program the return value is zero,
	// otherwise the return value is number of processes attached to the console
	const DWORD process_list = GetConsoleProcessList(processes.data(), static_cast<DWORD>(processes.size()));

	return static_cast<bool>(process_list);
}

Last edited on
Load function from DLL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string>
#include <Windows.h>

template <typename RETURN_TYPE, typename... ARGS>
[[nodiscard]] auto LoadFunction(const std::string& name, const std::string& dll) noexcept -> RETURN_TYPE(*)(ARGS...)
{
	HMODULE dll_module = LoadLibraryExA(
		dll.c_str(), // A string that specifies the file name of the module to load.
		NULL, // This parameter is reserved for future use. It must be NULL.
		LOAD_LIBRARY_SEARCH_SYSTEM32); // The action to be taken when loading the module.

	if (dll_module)
	{
		using FunctionExport = RETURN_TYPE (*)(ARGS...);

		// MSDN: Retrieves the address of an exported function or variable from the specified DLL.
		// If the function fails, the return value is NULL.
		// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa
		FunctionExport function = reinterpret_cast<FunctionExport>(GetProcAddress(dll_module, name.c_str()));

		if (function)
		{
			return function;
		}
	}

	return nullptr;
}


Example:

1
2
3
auto NtOpenProcess = LoadFunction<NTSTATUS, PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PCLIENT_ID>("NtOpenProcess", "NTDLL.DLL");

// Use NtOpenProcess function ... 
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<class It, class F>
It find_first_true(It begin, It end, F &f){
    if (begin >= end)
        return end;
    if (f(*begin))
        return begin;
    auto diff = end - begin;
    while (diff > 1){
        auto pivot = begin + diff / 2;
        if (!f(*pivot))
            begin = pivot;
        else
            end = pivot;
        diff = end - begin;
    }
    return end;
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
template <typename T>
void format_size(char *dst, T size){
    const char *prefixes[] = {
        "",
        "Ki",
        "Mi",
        "Gi",
        "Ti",
        "Pi",
        "Ei",
        "Zi",
        "Yi",
        "Xi",
        "Wi",
        "Vi",
        "Ui",
    };
    unsigned prefix = 0;
    while (size > 1 << 30){
        size /= 1024;
        prefix++;
    }
    double dsize = (double)size;
    while (dsize > 1024){
        dsize /= 1024;
        prefix++;
    }
    dsize = ceil(dsize * 10);
    
    {
        auto integer_part = (int)(dsize / 10);
        bool written = false;
        for (int power = 1000; power; power /= 10){
            if (written || integer_part >= power){
                *(dst++) = integer_part / power + '0';
                integer_part %= power;
                written = true;
            }
        }
        if (!written)
            *(dst++) = '0';
    }

    size = (int)dsize % 10;
    if (size){
        *(dst++) = '.';
        *(dst++) = (char)size + '0';
    }
    *(dst++) = ' ';
    for (auto p = prefixes[prefix]; *p; p++)
        *(dst++) = *p;
    *(dst++) = 'B';
    *(dst++) = 0;
}

template <typename T>
std::array<char, 32> format_size(T size){
    std::array<char, 32> ret;
    format_size(ret.data(), size);
    return ret;
}
I figured it could be useful to have some random snippets of code that might be useful here.


Perhaps some admin (?) would mark this thread as 'sticky' so that it stays at/near the top of the forum.
A simple class for use with range-for to allow begin/end to be specified

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename InputIter>
class RangeF {
public:
	constexpr RangeF(const InputIter& b, const InputIter& e) : beg_(b), end_(e) {}

	constexpr auto begin() const noexcept { return beg_; }
	constexpr auto end() const noexcept { return end_; }

private:
	InputIter beg_ {};
	InputIter end_ {};
};

#include <array>
#include <iostream>

int main()
{
	const std::array arr {1, 2, 3};

	for (const auto a : RangeF(arr.rbegin(), arr.rend()))
		std::cout << a << ' ';

	std::cout << '\n';
}

Last edited on
A couple of simple functions that ease keyboard input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
template<typename T = int>
typename std::enable_if_t< std::is_integral_v<T>, T>
getInp(const std::string& prm)
{
	const auto notsp {[&]() {while (std::isspace(static_cast<unsigned char>(std::cin.peek())) && std::cin.peek() != '\n') std::cin.ignore(); return std::cin.peek() != '\n'; }};
	T n {};

	while ((std::cout << prm) && (!(std::cin >> n) || notsp())) {
		std::cout << "Invalid input. Not a(n) " << typeid(T).name() << '\n';
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	}

	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	return n;
}

template<typename T = int>
typename std::enable_if_t< std::is_integral_v<T>, T>
getInp(const std::string& prm, T low, T upper = std::numeric_limits<T>::max())
{
	T num {};

	do {
		num = getInp<T>(prm);
	} while ((num < low || num > upper) && (std::cout << "Input not in range\n"));

	return num;
}

char getInp(const std::string& prm, const std::string& allwd)
{
	char ch {};

	do {
		ch = static_cast<char>(std::toupper(static_cast<unsigned char>(getInp<char>(prm))));
	} while ((allwd.find(ch) == std::string::npos) && (std::cout << "Invalid option\n"));

	return ch;
}

Toolkit header function to clear a Win console screen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Win32 SDK specific for clearing the console screen

#ifndef __CONSOLE_TOOLKIT_HPP__
#define __CONSOLE_TOOLKIT_HPP__

#include <windows.h>

inline void clear_screen(char fill = ' ')
{
   COORD                      crd { 0,0 };
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   HANDLE                     console { GetStdHandle(STD_OUTPUT_HANDLE) };
   GetConsoleScreenBufferInfo(console, &csbi);

   DWORD written;
   DWORD cells = csbi.dwSize.X * csbi.dwSize.Y;

   FillConsoleOutputCharacter(console, fill, cells, crd, &written);
   FillConsoleOutputAttribute(console, csbi.wAttributes, cells, crd, &written);

   SetConsoleCursorPosition(console, crd);
}

#endif 
In homage to Console Closing Down - http://www.cplusplus.com/forum/beginner/1988/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* user created functions and classes for flushing the input stream and pausing the program
 *
 * C++ Header: program_pauser_toolkit.hpp */

#ifndef __PROGRAM_PAUSER_TOOLKIT__
#define __PROGRAM_PAUSER_TOLLKIT__

#include <iostream>
#include <limits>

inline void _pause()
{
   std::cout << "\nPress ENTER to continue...";

   // clear any previous errors in the input stream
   std::cin.clear();

   // synchronize the input buffer stream
   std::cin.sync();

   // extract and discard the max number of characters
   // until an endline character is reached
   std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

class ProgramPauser
{
public:
     ProgramPauser() { }
    ~ProgramPauser() { _Pause(); }

public:
   inline void _Pause() const;
};

using PP = ProgramPauser;

inline void ProgramPauser::_Pause() const
{
   _pause();
}

#endif 
A simple menu toolkit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// "easy" menu utility

#ifndef __MENU_TOOLKIT__
#define __MENU_TOOLKIT__

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

namespace MTK // (Menu Tool Kit)
{
   inline void DisplayMenu(const std::vector<std::string>& stringArray)
   {
      std::cout << stringArray[0] << '\n';

      for (size_t i { 1 }; i < stringArray.size(); i++)
      {
         std::cout << " " << i << ") " << stringArray[i] << '\n';
      }
      std::cout << ": ";
   }

   inline unsigned short Menu(const std::vector<std::string>& stringArray)
   {
      unsigned short response { };
      size_t         size     { stringArray.size() - 1 };
      std::string    input    { };

      while (response < 1 || response > size)
      {
         DisplayMenu(stringArray);

         while (std::getline(std::cin, input))
         {
            std::istringstream is { input };

            if ((is >> response) && !(is >> input)) // re-using 'input' to test for extra stuff after the number
            {
               break; // done, we got what we wanted
            }
            std::cerr << "\n** INVALID CHOICE! **\n";
            DisplayMenu(stringArray);
         }

         if (response < 1 || response > size)
         {
            std::cerr << "\n** INVALID CHOICE! **\n";
         }
      }
      return response;
   }
}

#endif 
A C++ toolkit for trivial use to make using <random> (& <chrono>) almost as easy as using the C library random functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/* A simple toolkit to help beginners using <random> library an easier task */

// shamelessly stolen and adapted from a C++ working paper: WG21 N3551
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf

#ifndef __RANDOM_TOOLKIT__
#define __RANDOM_TOOLKIT__

#include <chrono>
#include <random>
#include <stdexcept>

namespace rtk
{
   static bool seeded { false };

   inline std::default_random_engine& urng()
   {
      static std::default_random_engine URNG { };

      return URNG;
   }

   inline void srand(bool FORCE_SEED = false)
   {
      static const std::seed_seq::result_type seeds[] { std::random_device {} (),
                                                        std::seed_seq::result_type(std::chrono::system_clock::now().time_since_epoch().count()) };
      static std::seed_seq sseq(std::begin(seeds), std::end(seeds));

      // the URNG can't be reseeded unless forced
      if (!seeded || FORCE_SEED)
      {
         urng().seed(sseq);

         seeded = true;
      }
   }

   inline void srand(signed seed, bool FORCE_SEED = false)
   {
      // the URNG can't be reseeded unless forced
      if (!seeded || FORCE_SEED)
      {
         urng().seed(static_cast<unsigned>(seed));

         seeded = true;
      }
   }

   // two function overloads to obtain uniform distribution ints and doubles
   inline int rand(int from, int to)
   {
      static std::uniform_int_distribution<> dist { };

      if (from > to) { throw std::invalid_argument("bad int params"); }

      return dist(urng(), decltype(dist)::param_type { from, to });
   }

   inline double rand(double from, double to)
   {
      static std::uniform_real_distribution<> dist { };

      if (from > to) { throw std::invalid_argument("bad double params"); }

      return dist(urng(), decltype(dist)::param_type { from, to });
   }

   // function for rolling dice, and checking if the # of pips is nonstandard
   inline int roll_die(int pips)
   {
      //check to see if the number of die pips is less than 2
      if (pips < 2)
      {
         return 0;
      }

      return rand(1, pips);
   }
}

#endif 
To enable delimited number stream insertion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <locale>
#include <string>
#include <iostream>

struct comma_facet : public std::numpunct<char> {
	explicit comma_facet(char del, uint8_t group) :g(group), delim(del) {}
	virtual char do_thousands_sep() const { return delim; }
	virtual std::string do_grouping() const { return std::string(1, g); }
	uint8_t g {};
	char delim {};
};

struct set000s {
public:
	explicit set000s(char del = ',', uint8_t grp = 3) : group(grp), delim(del) {}

private:
	uint8_t group {};
	char delim {};

	friend std::ostream& operator<<(std::ostream& os, const set000s& grp) {
		os.imbue(std::locale(os.getloc(), new comma_facet(grp.delim, grp.group)));
		return os;
	}
};

int main()
{
	std::cout << set000s('%') << 9999999 << '\n';
}



9%999%999

Please can someone make this thread 'sticky' so that it stays at the top.
That is unlikely to happen, unfortunately, and I cannot guarantee a timely edit to keep it near the top, alas.

Currently I am in no condition to post much -- won't be for another couple of weeks, so we'll see how it goes.

Once I can I'll start editing the top post to include a table of contents.
Another one:
1
2
3
#define CONCAT(x, y) x##y
#define CONCAT2(x, y) CONCAT(x, y)
#define LOCK_MUTEX(x) std::lock_guard<decltype(x)> CONCAT2(lg_, __COUNTER__)(x) 


Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::mutex m;
SomeThingThatLocksAndUnlocks m2;

void foo(){
    {
        LOCK_MUTEX(m);
        if (bar){
            LOCK_MUTEX(m2);
            //...
        }
    }
    LOCK_MUTEX(m2);
    //...
}
You should define CONCAT with the decorations invisible to the user:

1
2
#define CONCAT2(x,y) x##y
#define CONCAT(x,y) CONCAT2(x,y) 

Probably calling them something that's less likely to clash would also be good.
Both CAT and CONCAT are common. It this is to truly be a random useful things thread, the full definition should be:

1
2
3
4
#ifndef CONCAT
  #define CONCAT2(x,y) x##y
  #define CONCAT(x,y) CONCAT2(x,y)
#endif 

That won't stop all clashes from bad writers after you, but your hands are clean.
Pages: 12