diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000000000000000000000000000000000000..c03f3a3f034cfa48cb255e5a3582356f26e543f9
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,22 @@
+Checks: "clang-analyzer-*,
+         cppcoreguidelines-*,
+         misc-*,
+         modernize-*,
+         performance-*,
+         portability-*,
+         readability-*,
+         -cppcoreguidelines-avoid-c-arrays,
+         -cppcoreguidelines-avoid-magic-numbers,
+         -cppcoreguidelines-macro-usage,
+         -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+         -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+         -cppcoreguidelines-pro-type-union-access,
+         -misc-non-private-member-variables-in-classes,
+         -misc-no-recursion,
+         -modernize-avoid-c-arrays,
+         -modernize-use-trailing-return-type,
+         -readability-function-cognitive-complexity,
+         -readability-magic-numbers,
+         -readability-named-parameter"
+WarningsAsErrors: "*"
+HeaderFilterRegex: "src/"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3f8304e375e1442932b0cf79e2855505c74690d3..5d087f8644c61f7e14d89fe3cde7ffc17f12cb87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,19 +1,24 @@
 cmake_minimum_required(VERSION 3.8)
-
-project(
-	"B-ASIC"
+project("B-ASIC"
 	VERSION 1.0.0
 	DESCRIPTION "Better ASIC Toolbox for Python 3"
-	LANGUAGES C CXX
-)
+	LANGUAGES C CXX)
 
-# Find dependencies.
-find_package(fmt REQUIRED)
-find_package(pybind11 CONFIG REQUIRED)
+option(ASIC_USE_FETCHCONTENT "Automatically download dependencies" ON)
+option(ASIC_USE_CLANG_TIDY "Use clang-tidy for static analysis" OFF)
+option(ASIC_BUILDING_PYTHON_DISTRIBUTION "Don't copy compiled binaries to project directory" OFF)
 
 set(LIBRARY_NAME "b_asic") # Name of the python library directory.
 set(TARGET_NAME "_${LIBRARY_NAME}") # Name of this extension module.
 
+# Find dependencies.
+if(ASIC_USE_FETCHCONTENT)
+	add_subdirectory(dependencies)
+else()
+	find_package(fmt REQUIRED)
+	find_package(pybind11 CONFIG REQUIRED)
+endif()
+
 # Set output directory for compiled binaries.
 if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
 	include(GNUInstallDirs)
@@ -32,9 +37,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
 
 # Add files to be compiled into Python module.
-pybind11_add_module(
-	"${TARGET_NAME}"
-
+pybind11_add_module("${TARGET_NAME}"
 	# Main files.
 	"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation.cpp"
@@ -53,49 +56,44 @@ pybind11_add_module(
 )
 
 # Include headers.
-target_include_directories(
-	"${TARGET_NAME}"
-    PRIVATE
-        "${CMAKE_CURRENT_SOURCE_DIR}/src"
-)
+target_include_directories("${TARGET_NAME}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
 
 # Use C++17.
-target_compile_features(
-	"${TARGET_NAME}"
-	PRIVATE
-		cxx_std_17
-)
+target_compile_features("${TARGET_NAME}" PRIVATE cxx_std_17)
 
 # Set compiler-specific options using generator expressions.
-target_compile_options(
-	"${TARGET_NAME}"
-	PRIVATE
-		$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>:
-			-W -Wall -Wextra -Werror -Wno-psabi
-			$<$<CONFIG:Debug>:-g>
-			$<$<NOT:$<CONFIG:Debug>>:-O3>
-		>
-		$<$<CXX_COMPILER_ID:MSVC>:
-			/W3 /WX /permissive- /utf-8
-			$<$<CONFIG:Debug>:/Od>
-			$<$<NOT:$<CONFIG:Debug>>:/Ot>
-		>
-)
+target_compile_options("${TARGET_NAME}" PRIVATE
+	$<$<CXX_COMPILER_ID:GNU>:   -std=c++17  -Wall -Wextra   -Wpedantic      -Werror -Wno-psabi      $<$<CONFIG:Debug>:-Og -g>   $<$<CONFIG:Release>:-O3>    $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
+	$<$<CXX_COMPILER_ID:Clang>: -std=c++17  -Wall -Wextra   -Wpedantic      -Werror                 $<$<CONFIG:Debug>:-Og -g>   $<$<CONFIG:Release>:-O3>    $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
+	$<$<CXX_COMPILER_ID:MSVC>:  /std:c++17  /W3             /permissive-    /WX     /wd4996 /utf-8  $<$<CONFIG:Debug>:/Od>      $<$<CONFIG:Release>:/Ot>    $<$<CONFIG:MinSizeRel>:/Os> $<$<CONFIG:RelWithDebInfo>:/Ot /Od>>)
 
 # Add libraries. Note: pybind11 is already added in pybind11_add_module.
-target_link_libraries(
-	"${TARGET_NAME}"
-	PRIVATE
+if(ASIC_USE_FETCHCONTENT)
+	target_link_libraries("${TARGET_NAME}" PRIVATE
+		dependency_fmt)
+else()
+	target_link_libraries("${TARGET_NAME}" PRIVATE
 		$<TARGET_NAME_IF_EXISTS:fmt::fmt-header-only>
-		$<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt>
-)
+		$<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt>)
+endif()
+
+# Set up clang-tidy.
+if(ASIC_USE_CLANG_TIDY)
+	find_program(CLANG_TIDY NAMES clang-tidy REQUIRED)
+	set_property(TARGET "${TARGET_NAME}" PROPERTY CXX_CLANG_TIDY ${CLANG_TIDY})
+	if(MSVC)
+		set_target_properties("${TARGET_NAME}" PROPERTIES
+			VS_GLOBAL_RunCodeAnalysis true
+			VS_GLOBAL_EnableMicrosoftCodeAnalysis false
+			VS_GLOBAL_EnableClangTidyCodeAnalysis true
+			VS_GLOBAL_ClangTidyToolExe "${CLANG_TIDY}")
+	endif()
+endif()
 
 # Copy binaries to project folder for debugging during development.
 if(NOT ASIC_BUILDING_PYTHON_DISTRIBUTION)
-	add_custom_target(
-		copy_binaries ALL
+	add_custom_target(copy_binaries ALL
 		COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${TARGET_NAME}>" "${CMAKE_CURRENT_LIST_DIR}"
 		COMMENT "Copying binaries to ${CMAKE_CURRENT_LIST_DIR}"
-		DEPENDS "${TARGET_NAME}"
-	)
+		DEPENDS "${TARGET_NAME}")
 endif()
diff --git a/MANIFEST.in b/MANIFEST.in
index 96b265fd04394abbdbf033028b46c98619817869..36ba9fca42e0017fece97fcf50bd793cd372eda8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,4 +2,4 @@ include README.md
 include LICENSE
 include CMakeLists.txt
 include b_asic/GUI/operation_icons/*
-recursive-include src *.cpp *.h
+recursive-include src *.cpp *.hpp
diff --git a/README.md b/README.md
index f0743ea53ec24c5b1b62fb7d845b0008f8f45a9d..36bfe17d7ba25dd3794816db13aee0c19dfbd999 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,55 @@
 <img src="logo.png" width="278" height="100">
 
 # B-ASIC - Better ASIC Toolbox
+
 B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization.
 
 ## Development
+
 How to build and debug the library during development.
 
 ### Prerequisites
+
 The following packages are required in order to build the library:
-* cmake 3.8+
-* gcc 7+/clang 7+/msvc 16+
-* fmtlib
-* pybind11 2.3.0+
-* python 3.6+
-* Python:
-  * graphviz
-  * matplotlib
-  * numpy
-  * pybind11
-  * pyside2
-  * qtpy
-  * scipy
-  * setuptools
+
+-   cmake 3.8+
+-   gcc 7+/clang 7+/msvc 16+
+-   fmtlib
+-   pybind11 2.3.0+
+-   python 3.6+
+-   Python:
+    -   graphviz
+    -   matplotlib
+    -   numpy
+    -   pybind11
+    -   pyside2
+    -   qtpy
+    -   scipy
+    -   setuptools
 
 To build a binary distribution, the following additional packages are required:
-* Python:
-  * wheel
+
+-   Python:
+    -   wheel
 
 To run the test suite, the following additional packages are required:
-* Python:
-  * pytest
-  * pytest-cov (for testing with coverage)
-  
+
+-   Python:
+    -   pytest
+    -   pytest-cov (for testing with coverage)
+
 To generate the documentation, the following additional packages are required:
-* doxygen
+
+-   doxygen
 
 ### Using CMake directly
+
 How to build using CMake.
 
 #### Configuring
+
 In `B-ASIC`:
+
 ```
 mkdir build
 cd build
@@ -47,53 +57,73 @@ cmake ..
 ```
 
 #### Building (Debug)
+
 In `B-ASIC/build`:
+
 ```
 cmake --build .
 ```
+
 The output gets written to `B-ASIC/build/lib`.
 
 #### Building (Release)
+
 In `B-ASIC/build`:
+
 ```
 cmake --build . --config Release
 ```
+
 The output gets written to `B-ASIC/build/lib`.
 
 ### Using setuptools to create a package
+
 How to create a package using setuptools that can be installed using pip.
 
 #### Setup (Binary distribution)
+
 In `B-ASIC`:
+
 ```
 python3 setup.py bdist_wheel
 ```
+
 The output gets written to `B-ASIC/dist/b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl`.
 
 #### Setup (Source distribution)
+
 In `B-ASIC`:
+
 ```
 python3 setup.py sdist
 ```
+
 The output gets written to `B-ASIC/dist/b-asic-<version>.tar.gz`.
 
 #### Installation (Binary distribution)
+
 In `B-ASIC/dist`:
+
 ```
 pip install b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl
 ```
 
 #### Installation (Source distribution)
+
 In `B-ASIC/dist`:
+
 ```
 pip install b-asic-<version>.tar.gz
 ```
 
 ### Running tests
+
 How to run the tests using pytest in a virtual environment.
 
 #### Linux/OS X
+
 In `B-ASIC`:
+
 ```
 python3 -m venv env
 source env/bin/activate
@@ -102,7 +132,9 @@ pytest
 ```
 
 #### Windows
+
 In `B-ASIC` (as admin):
+
 ```
 python3 -m venv env
 .\env\Scripts\activate.bat
@@ -111,26 +143,33 @@ pytest
 ```
 
 #### Test with coverage
+
 ```
 pytest --cov=b_asic --cov-report html test
 ```
 
 ### Generating documentation
+
 In `B-ASIC`:
+
 ```
 doxygen
 ```
+
 The output gets written to `B-ASIC/doc`.
 
 ## Usage
+
 How to build and use the library as a user.
 
 ### Installation
+
 ```
 pip install b_asic
 ```
 
 ### Importing
+
 ```
 python3
 >>> import b_asic as asic
@@ -138,5 +177,6 @@ python3
 ```
 
 ## License
+
 B-ASIC is distributed under the MIT license.
 See the included LICENSE file for more information.
diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..229a492f5ce8c0f1e232fffa3a06e334d057d686
--- /dev/null
+++ b/dependencies/CMakeLists.txt
@@ -0,0 +1,4 @@
+include(FetchContent)
+
+add_subdirectory(fmt)
+add_subdirectory(pybind11)
diff --git a/dependencies/fmt/CMakeLists.txt b/dependencies/fmt/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fda9e099476f8f7c29878a61253623ca3e1b8699
--- /dev/null
+++ b/dependencies/fmt/CMakeLists.txt
@@ -0,0 +1,11 @@
+message(STATUS "Fetching fmt...")
+
+FetchContent_Declare(fmt
+	GIT_REPOSITORY https://github.com/fmtlib/fmt
+	GIT_TAG d141cdbeb0fb422a3fb7173b285fd38e0d1772dc # 8.0.1
+)
+FetchContent_MakeAvailable(fmt)
+
+add_library(dependency_fmt INTERFACE)
+target_include_directories(dependency_fmt SYSTEM INTERFACE $<TARGET_PROPERTY:fmt-header-only,INTERFACE_INCLUDE_DIRECTORIES>)
+target_link_libraries(dependency_fmt INTERFACE fmt-header-only)
diff --git a/dependencies/pybind11/CMakeLists.txt b/dependencies/pybind11/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dea520bc8c89a1793c6b609c1d075a286ed37289
--- /dev/null
+++ b/dependencies/pybind11/CMakeLists.txt
@@ -0,0 +1,7 @@
+message(STATUS "Fetching pybind11...")
+
+FetchContent_Declare(pybind11
+	GIT_REPOSITORY https://github.com/pybind/pybind11
+	GIT_TAG f7b499615e14d70ab098a20deb0cdb3889998a1a # 2.8.1
+)
+FetchContent_MakeAvailable(pybind11)
diff --git a/legacy/README.md b/legacy/README.md
index 746d1efdb57c04f4e52bb2ce7f0a7def99c744ed..a450266c51265e81844e9c15353e99d90c798d8e 100644
--- a/legacy/README.md
+++ b/legacy/README.md
@@ -8,4 +8,4 @@ or to be used as a refererence for future development.
 This folder contains a C++ implementation of the Simulation class designed
 using Object-Oriented Programming, as opposed to the current version that uses
 Data-Oriented Design. They are functionally identical, but use different
-styles of programming and have different performance characteristics.
\ No newline at end of file
+styles of programming and have different performance characteristics.
diff --git a/legacy/simulation_oop/core_operations.h b/legacy/simulation_oop/core_operations.hpp
similarity index 96%
rename from legacy/simulation_oop/core_operations.h
rename to legacy/simulation_oop/core_operations.hpp
index 0926572905053cdf5c762f73545642f1ffe3d4f8..16b05746d436f3e59c0766879c0cf723d9ff459f 100644
--- a/legacy/simulation_oop/core_operations.h
+++ b/legacy/simulation_oop/core_operations.hpp
@@ -1,10 +1,9 @@
-#ifndef ASIC_SIMULATION_CORE_OPERATIONS_H
-#define ASIC_SIMULATION_CORE_OPERATIONS_H
+#ifndef ASIC_SIMULATION_CORE_OPERATIONS_HPP
+#define ASIC_SIMULATION_CORE_OPERATIONS_HPP
 
-#define NOMINMAX
-#include "../debug.h"
-#include "../number.h"
-#include "operation.h"
+#include "../debug.hpp"
+#include "../number.hpp"
+#include "operation.hpp"
 
 #include <algorithm>
 #include <cmath>
@@ -233,4 +232,4 @@ private:
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_CORE_OPERATIONS_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_CORE_OPERATIONS_HPP
diff --git a/legacy/simulation_oop/custom_operation.cpp b/legacy/simulation_oop/custom_operation.cpp
index 9153eb5b651f3a481747f3b652f790ce581e70dc..03fbb4b7c8b8790c0004b885736609d9be4d6667 100644
--- a/legacy/simulation_oop/custom_operation.cpp
+++ b/legacy/simulation_oop/custom_operation.cpp
@@ -1,9 +1,8 @@
-#include "custom_operation.h"
+#include "custom_operation.hpp"
 
+#define NOMINMAX
 #include <pybind11/stl.h>
 
-namespace py = pybind11;
-
 namespace asic {
 
 custom_operation::custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input,
@@ -27,4 +26,4 @@ number custom_operation::truncate_input(std::size_t index, number value, std::si
 	return m_truncate_input(index, value, bits).cast<number>();
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/legacy/simulation_oop/custom_operation.h b/legacy/simulation_oop/custom_operation.hpp
similarity index 74%
rename from legacy/simulation_oop/custom_operation.h
rename to legacy/simulation_oop/custom_operation.hpp
index 8a11aaacbc8c17500069522d9d8f56d9c416d804..12a2a107b7b08c9e6752e61759cb0092937e1eba 100644
--- a/legacy/simulation_oop/custom_operation.h
+++ b/legacy/simulation_oop/custom_operation.hpp
@@ -1,11 +1,12 @@
-#ifndef ASIC_SIMULATION_CUSTOM_OPERATION_H
-#define ASIC_SIMULATION_CUSTOM_OPERATION_H
+#ifndef ASIC_SIMULATION_CUSTOM_OPERATION_HPP
+#define ASIC_SIMULATION_CUSTOM_OPERATION_HPP
 
-#include "../algorithm.h"
-#include "../debug.h"
-#include "../number.h"
-#include "operation.h"
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "../number.hpp"
+#include "operation.hpp"
 
+#define NOMINMAX
 #include <cstddef>
 #include <fmt/format.h>
 #include <functional>
@@ -32,4 +33,4 @@ private:
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_CUSTOM_OPERATION_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_CUSTOM_OPERATION_HPP
diff --git a/legacy/simulation_oop/operation.cpp b/legacy/simulation_oop/operation.cpp
index a9738a0a05287f6ab2d430d4c73560a4c6bd57c5..57a187f8485ceb3f27ee2aceee1fede7c9b4e615 100644
--- a/legacy/simulation_oop/operation.cpp
+++ b/legacy/simulation_oop/operation.cpp
@@ -1,7 +1,8 @@
-#include "operation.h"
+#include "operation.hpp"
 
-#include "../debug.h"
+#include "../debug.hpp"
 
+#define NOMINMAX
 #include <pybind11/pybind11.h>
 
 namespace py = pybind11;
@@ -11,7 +12,7 @@ namespace asic {
 signal_source::signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits)
 	: m_operation(std::move(op))
 	, m_index(index)
-	, m_bits(std::move(bits)) {}
+	, m_bits(bits) {}
 
 signal_source::operator bool() const noexcept {
 	return static_cast<bool>(m_operation);
@@ -100,7 +101,7 @@ signal_source const& unary_operation::input() const noexcept {
 number unary_operation::evaluate_input(evaluation_context const& context) const {
 	auto const value = m_in.evaluate_output(context);
 	auto const bits = context.bits_override.value_or(m_in.bits().value_or(0));
-	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+	return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
 }
 
 binary_operation::binary_operation(result_key key)
@@ -122,13 +123,13 @@ signal_source const& binary_operation::rhs() const noexcept {
 number binary_operation::evaluate_lhs(evaluation_context const& context) const {
 	auto const value = m_lhs.evaluate_output(context);
 	auto const bits = context.bits_override.value_or(m_lhs.bits().value_or(0));
-	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+	return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
 }
 
 number binary_operation::evaluate_rhs(evaluation_context const& context) const {
 	auto const value = m_rhs.evaluate_output(context);
 	auto const bits = context.bits_override.value_or(m_rhs.bits().value_or(0));
-	return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value;
+	return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
 }
 
 nary_operation::nary_operation(result_key key)
@@ -148,9 +149,9 @@ std::vector<number> nary_operation::evaluate_inputs(evaluation_context const& co
 	for (auto const& input : m_inputs) {
 		auto const value = input.evaluate_output(context);
 		auto const bits = context.bits_override.value_or(input.bits().value_or(0));
-		values.push_back((context.truncate && bits) ? this->truncate_input(0, value, bits) : value);
+		values.push_back((context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value);
 	}
 	return values;
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/legacy/simulation_oop/operation.h b/legacy/simulation_oop/operation.hpp
similarity index 72%
rename from legacy/simulation_oop/operation.h
rename to legacy/simulation_oop/operation.hpp
index 344eacc1482c40021b3d0ff686cbef5c71085f58..086666102a14e32895834c0616af1728e48dc400 100644
--- a/legacy/simulation_oop/operation.h
+++ b/legacy/simulation_oop/operation.hpp
@@ -1,8 +1,8 @@
-#ifndef ASIC_SIMULATION_OPERATION_H
-#define ASIC_SIMULATION_OPERATION_H
+#ifndef ASIC_SIMULATION_OPERATION_HPP
+#define ASIC_SIMULATION_OPERATION_HPP
 
-#include "../number.h"
-#include "../span.h"
+#include "../number.hpp"
+#include "../span.hpp"
 
 #include <cstddef>
 #include <cstdint>
@@ -26,11 +26,11 @@ using delay_map = std::unordered_map<result_key, number>;
 using delay_queue = std::vector<std::pair<result_key, signal_source const*>>;
 
 struct evaluation_context final {
-	result_map* results;
-	delay_map* delays;
-	delay_queue* deferred_delays;
-	std::optional<std::size_t> bits_override;
-	bool truncate;
+	result_map* results = nullptr;
+	delay_map* delays = nullptr;
+	delay_queue* deferred_delays = nullptr;
+	std::optional<std::size_t> bits_override{};
+	bool truncate = false;
 };
 
 class signal_source final {
@@ -46,23 +46,25 @@ public:
 	[[nodiscard]] std::optional<std::size_t> bits() const noexcept;
 
 private:
-	std::shared_ptr<const operation> m_operation;
+	std::shared_ptr<const operation> m_operation{};
 	std::size_t m_index = 0;
-	std::optional<std::size_t> m_bits;
+	std::optional<std::size_t> m_bits{};
 };
 
-class operation {
+class operation { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
+	operation() noexcept = default;
 	virtual ~operation() = default;
+
 	[[nodiscard]] virtual std::size_t output_count() const noexcept = 0;
 	[[nodiscard]] virtual std::optional<number> current_output(std::size_t index, delay_map const& delays) const = 0;
 	[[nodiscard]] virtual number evaluate_output(std::size_t index, evaluation_context const& context) const = 0;
 };
 
-class abstract_operation : public operation {
+class abstract_operation : public operation { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
 	explicit abstract_operation(result_key key);
-	virtual ~abstract_operation() = default;
+	~abstract_operation() override = default;
 
 	[[nodiscard]] std::optional<number> current_output(std::size_t, delay_map const&) const override;
 	[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const override;
@@ -78,10 +80,10 @@ private:
 	result_key m_key;
 };
 
-class unary_operation : public abstract_operation {
+class unary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
 	explicit unary_operation(result_key key);
-	virtual ~unary_operation() = default;
+	~unary_operation() override = default;
 
 	void connect(signal_source in);
 
@@ -94,10 +96,10 @@ private:
 	signal_source m_in;
 };
 
-class binary_operation : public abstract_operation {
+class binary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
 	explicit binary_operation(result_key key);
-	virtual ~binary_operation() = default;
+	~binary_operation() override = default;
 
 	void connect(signal_source lhs, signal_source rhs);
 
@@ -112,10 +114,10 @@ private:
 	signal_source m_rhs;
 };
 
-class nary_operation : public abstract_operation {
+class nary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
 	explicit nary_operation(result_key key);
-	virtual ~nary_operation() = default;
+	~nary_operation() override = default;
 
 	void connect(std::vector<signal_source> inputs);
 
@@ -124,9 +126,9 @@ protected:
 	[[nodiscard]] std::vector<number> evaluate_inputs(evaluation_context const& context) const;
 
 private:
-	std::vector<signal_source> m_inputs;
+	std::vector<signal_source> m_inputs{};
 };
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_OPERATION_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_OPERATION_HPP
diff --git a/legacy/simulation_oop/signal_flow_graph.cpp b/legacy/simulation_oop/signal_flow_graph.cpp
index 4c3763c81e8f97f22caf42a47d88c1186b9a874d..5248de741cff3bc7a6e03fc3e479998820c42707 100644
--- a/legacy/simulation_oop/signal_flow_graph.cpp
+++ b/legacy/simulation_oop/signal_flow_graph.cpp
@@ -1,6 +1,6 @@
-#include "signal_flow_graph.h"
+#include "signal_flow_graph.hpp"
 
-#include "../debug.h"
+#include "../debug.hpp"
 
 namespace py = pybind11;
 
@@ -67,8 +67,8 @@ std::shared_ptr<custom_operation> signal_flow_graph_operation::add_custom_operat
 																					std::string_view prefix, result_key key) {
 	auto const input_count = op.attr("input_count").cast<std::size_t>();
 	auto const output_count = op.attr("output_count").cast<std::size_t>();
-	auto const new_op = add_operation<custom_operation>(
-		op, added, key, op.attr("evaluate_output"), op.attr("truncate_input"), output_count);
+	auto new_op = add_operation<custom_operation>(
+		op, added, std::move(key), op.attr("evaluate_output"), op.attr("truncate_input"), output_count);
 	auto inputs = std::vector<signal_source>{};
 	inputs.reserve(input_count);
 	for (auto const i : range(input_count)) {
@@ -141,4 +141,4 @@ std::shared_ptr<operation> signal_flow_graph_operation::make_operation(pybind11:
 	return add_custom_operation(op, added, prefix, std::move(key));
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/legacy/simulation_oop/signal_flow_graph.h b/legacy/simulation_oop/signal_flow_graph.hpp
similarity index 82%
rename from legacy/simulation_oop/signal_flow_graph.h
rename to legacy/simulation_oop/signal_flow_graph.hpp
index f06788249e367d3e8e0602f04c6dcf91c71b7a96..c6593af0c9df9a125a477144d08cf546e1b42370 100644
--- a/legacy/simulation_oop/signal_flow_graph.h
+++ b/legacy/simulation_oop/signal_flow_graph.hpp
@@ -1,14 +1,15 @@
-#ifndef ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
-#define ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
-
-#include "../algorithm.h"
-#include "../debug.h"
-#include "../number.h"
-#include "core_operations.h"
-#include "custom_operation.h"
-#include "operation.h"
-#include "special_operations.h"
-
+#ifndef ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
+#define ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
+
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "../number.hpp"
+#include "core_operations.hpp"
+#include "custom_operation.hpp"
+#include "operation.hpp"
+#include "special_operations.hpp"
+
+#define NOMINMAX
 #include <Python.h>
 #include <cstddef>
 #include <fmt/format.h>
@@ -51,7 +52,7 @@ private:
 	template <typename Operation, typename... Args>
 	[[nodiscard]] static std::shared_ptr<Operation> add_unary_operation(pybind11::handle op, added_operation_cache& added,
 																		std::string_view prefix, Args&&... args) {
-		auto const new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
+		auto new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
 		new_op->connect(make_source(op, 0, added, prefix));
 		return new_op;
 	}
@@ -59,7 +60,7 @@ private:
 	template <typename Operation, typename... Args>
 	[[nodiscard]] static std::shared_ptr<Operation> add_binary_operation(pybind11::handle op, added_operation_cache& added,
 																		 std::string_view prefix, Args&&... args) {
-		auto const new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
+		auto new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
 		new_op->connect(make_source(op, 0, added, prefix), make_source(op, 1, added, prefix));
 		return new_op;
 	}
@@ -73,10 +74,10 @@ private:
 	[[nodiscard]] static std::shared_ptr<operation> make_operation(pybind11::handle op, added_operation_cache& added,
 																   std::string_view prefix);
 
-	std::vector<output_operation> m_output_operations;
-	std::vector<std::shared_ptr<input_operation>> m_input_operations;
+	std::vector<output_operation> m_output_operations{};
+	std::vector<std::shared_ptr<input_operation>> m_input_operations{};
 };
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
diff --git a/legacy/simulation_oop/simulation.cpp b/legacy/simulation_oop/simulation.cpp
index 4d6ff83337a6dade0a0d476e5942ee3b14178195..7d044c6a8a76b5d845f47342b2a82ec9a241a5b4 100644
--- a/legacy/simulation_oop/simulation.cpp
+++ b/legacy/simulation_oop/simulation.cpp
@@ -1,15 +1,13 @@
-#define NOMINMAX
-#include "simulation.h"
+#include "simulation.hpp"
 
-#include "../debug.h"
+#include "../debug.hpp"
 
 namespace py = pybind11;
 
 namespace asic {
 
-simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers)
-	: m_sfg("")
-	, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_t) -> number { return number{}; }) {
+simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers)
+	: m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_type) -> number { return number{}; }) {
 	if (input_providers) {
 		this->set_inputs(std::move(*input_providers));
 	}
@@ -17,29 +15,30 @@ simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::opti
 	m_sfg.create(sfg, added);
 }
 
-void simulation::set_input(std::size_t index, input_provider_t input_provider) {
+void simulation::set_input(std::size_t index, input_provider_type input_provider) {
 	if (index >= m_input_functions.size()) {
 		throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
 	}
-	if (auto* const callable = std::get_if<input_function_t>(&input_provider)) {
+	if (auto* const callable = std::get_if<input_function_type>(&input_provider)) {
 		m_input_functions[index] = std::move(*callable);
 	} else if (auto* const numeric = std::get_if<number>(&input_provider)) {
-		m_input_functions[index] = [value = *numeric](iteration_t) -> number {
+		m_input_functions[index] = [value = *numeric](iteration_type) -> number {
 			return value;
 		};
 	} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
 		if (!m_input_length) {
-			m_input_length = static_cast<iteration_t>(list->size());
-		} else if (*m_input_length != static_cast<iteration_t>(list->size())) {
+			m_input_length = static_cast<iteration_type>(list->size());
+		} else if (*m_input_length != static_cast<iteration_type>(list->size())) {
 			throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
 		}
-		m_input_functions[index] = [values = std::move(*list)](iteration_t n) -> number {
+		m_input_functions[index] = [values = std::move(*list)](iteration_type n) -> number {
 			return values.at(n);
 		};
 	}
 }
 
-void simulation::set_inputs(std::vector<std::optional<input_provider_t>> input_providers) {
+void simulation::set_inputs(
+	std::vector<std::optional<input_provider_type>> input_providers) { // NOLINT(performance-unnecessary-value-param)
 	if (input_providers.size() != m_input_functions.size()) {
 		throw py::value_error{fmt::format(
 			"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
@@ -55,7 +54,7 @@ std::vector<number> simulation::step(bool save_results, std::optional<std::size_
 	return this->run_for(1, save_results, bits_override, truncate);
 }
 
-std::vector<number> simulation::run_until(iteration_t iteration, bool save_results, std::optional<std::size_t> bits_override,
+std::vector<number> simulation::run_until(iteration_type iteration, bool save_results, std::optional<std::size_t> bits_override,
 										  bool truncate) {
 	auto result = std::vector<number>{};
 	while (m_iteration < iteration) {
@@ -100,9 +99,9 @@ std::vector<number> simulation::run_until(iteration_t iteration, bool save_resul
 	return result;
 }
 
-std::vector<number> simulation::run_for(iteration_t iterations, bool save_results, std::optional<std::size_t> bits_override,
+std::vector<number> simulation::run_for(iteration_type iterations, bool save_results, std::optional<std::size_t> bits_override,
 										bool truncate) {
-	if (iterations > std::numeric_limits<iteration_t>::max() - m_iteration) {
+	if (iterations > std::numeric_limits<iteration_type>::max() - m_iteration) {
 		throw py::value_error("Simulation iteration type overflow!");
 	}
 	return this->run_until(m_iteration + iterations, save_results, bits_override, truncate);
@@ -115,7 +114,7 @@ std::vector<number> simulation::run(bool save_results, std::optional<std::size_t
 	throw py::index_error{"Tried to run unlimited simulation"};
 }
 
-iteration_t simulation::iteration() const noexcept {
+iteration_type simulation::iteration() const noexcept {
 	return m_iteration;
 }
 
diff --git a/legacy/simulation_oop/simulation.h b/legacy/simulation_oop/simulation.h
deleted file mode 100644
index 38e2771b877772bd28048cae16d791bb4e0b45e3..0000000000000000000000000000000000000000
--- a/legacy/simulation_oop/simulation.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#ifndef ASIC_SIMULATION_OOP_H
-#define ASIC_SIMULATION_OOP_H
-
-#include "../number.h"
-#include "core_operations.h"
-#include "custom_operation.h"
-#include "operation.h"
-#include "signal_flow_graph.h"
-#include "special_operations.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <fmt/format.h>
-#include <functional>
-#include <limits>
-#include <memory>
-#include <optional>
-#include <pybind11/functional.h>
-#include <pybind11/numpy.h>
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <string_view>
-#include <unordered_map>
-#include <utility>
-#include <variant>
-#include <vector>
-
-namespace asic {
-
-using iteration_t = std::uint32_t;
-using result_array_map = std::unordered_map<std::string, std::vector<number>>;
-using input_function_t = std::function<number(iteration_t)>;
-using input_provider_t = std::variant<number, std::vector<number>, input_function_t>;
-
-class simulation final {
-public:
-	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers = std::nullopt);
-
-	void set_input(std::size_t index, input_provider_t input_provider);
-	void set_inputs(std::vector<std::optional<input_provider_t>> input_providers);
-
-	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
-	[[nodiscard]] std::vector<number> run_until(iteration_t iteration, bool save_results, std::optional<std::size_t> bits_override,
-												bool truncate);
-	[[nodiscard]] std::vector<number> run_for(iteration_t iterations, bool save_results, std::optional<std::size_t> bits_override,
-											  bool truncate);
-	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
-
-	[[nodiscard]] iteration_t iteration() const noexcept;
-	[[nodiscard]] pybind11::dict results() const noexcept;
-
-	void clear_results() noexcept;
-	void clear_state() noexcept;
-
-private:
-	signal_flow_graph_operation m_sfg;
-	result_array_map m_results;
-	delay_map m_delays;
-	iteration_t m_iteration = 0;
-	std::vector<input_function_t> m_input_functions;
-	std::optional<iteration_t> m_input_length;
-};
-
-} // namespace asic
-
-#endif // ASIC_SIMULATION_OOP_H
\ No newline at end of file
diff --git a/legacy/simulation_oop/simulation.hpp b/legacy/simulation_oop/simulation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e10feb990c90ae314576a227d284c5ca2fc4f8ce
--- /dev/null
+++ b/legacy/simulation_oop/simulation.hpp
@@ -0,0 +1,67 @@
+#ifndef ASIC_SIMULATION_OOP_HPP
+#define ASIC_SIMULATION_OOP_HPP
+
+#include "../number.hpp"
+#include "core_operations.hpp"
+#include "custom_operation.hpp"
+#include "operation.hpp"
+#include "signal_flow_graph.hpp"
+#include "special_operations.hpp"
+
+#define NOMINMAX
+#include <cstddef>
+#include <cstdint>
+#include <fmt/format.h>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <pybind11/functional.h>
+#include <pybind11/numpy.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace asic {
+
+using iteration_type = std::uint32_t;
+using result_array_map = std::unordered_map<std::string, std::vector<number>>;
+using input_function_type = std::function<number(iteration_type)>;
+using input_provider_type = std::variant<number, std::vector<number>, input_function_type>;
+
+class simulation final {
+public:
+	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers = std::nullopt);
+
+	void set_input(std::size_t index, input_provider_type input_provider);
+	void set_inputs(std::vector<std::optional<input_provider_type>> input_providers);
+
+	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
+	[[nodiscard]] std::vector<number> run_until(iteration_type iteration, bool save_results, std::optional<std::size_t> bits_override,
+												bool truncate);
+	[[nodiscard]] std::vector<number> run_for(iteration_type iterations, bool save_results, std::optional<std::size_t> bits_override,
+											  bool truncate);
+	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
+
+	[[nodiscard]] iteration_type iteration() const noexcept;
+	[[nodiscard]] pybind11::dict results() const noexcept;
+
+	void clear_results() noexcept;
+	void clear_state() noexcept;
+
+private:
+	signal_flow_graph_operation m_sfg{""};
+	result_array_map m_results{};
+	delay_map m_delays{};
+	iteration_type m_iteration = 0;
+	std::optional<iteration_type> m_input_length{};
+	std::vector<input_function_type> m_input_functions;
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_OOP_HPP
diff --git a/legacy/simulation_oop/special_operations.cpp b/legacy/simulation_oop/special_operations.cpp
index 1f7a6519a90ba08224585e36093694becf495c4d..0a7100d954049979f59a773124fda6034fd892a3 100644
--- a/legacy/simulation_oop/special_operations.cpp
+++ b/legacy/simulation_oop/special_operations.cpp
@@ -1,6 +1,6 @@
-#include "special_operations.h"
+#include "special_operations.hpp"
 
-#include "../debug.h"
+#include "../debug.hpp"
 
 namespace asic {
 
diff --git a/legacy/simulation_oop/special_operations.h b/legacy/simulation_oop/special_operations.hpp
similarity index 86%
rename from legacy/simulation_oop/special_operations.h
rename to legacy/simulation_oop/special_operations.hpp
index 88fb087e84378e36f423364d2c7d83a083828784..8c16c01a0e51cd956ee3c556548029c5b17d4be3 100644
--- a/legacy/simulation_oop/special_operations.h
+++ b/legacy/simulation_oop/special_operations.hpp
@@ -1,9 +1,9 @@
-#ifndef ASIC_SIMULATION_SPECIAL_OPERATIONS_H
-#define ASIC_SIMULATION_SPECIAL_OPERATIONS_H
+#ifndef ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
+#define ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
 
-#include "../debug.h"
-#include "../number.h"
-#include "operation.h"
+#include "../debug.hpp"
+#include "../number.hpp"
+#include "operation.hpp"
 
 #include <cassert>
 #include <cstddef>
@@ -52,4 +52,4 @@ private:
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_SPECIAL_OPERATIONS_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
diff --git a/setup.py b/setup.py
index 41376eb294fdee980533063ac4e207e67750b20b..c412d60e6645f8f224d44863bdf5cb53853d1d48 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ class CMakeBuild(build_ext):
             os.path.dirname(self.get_ext_fullpath(ext.name)))
         cmake_configure_argv = [
             CMAKE_EXE, ext.sourcedir,
-            "-DASIC_BUILDING_PYTHON_DISTRIBUTION=true",
+            "-DASIC_BUILDING_PYTHON_DISTRIBUTION=ON",
             "-DCMAKE_BUILD_TYPE=" + cmake_build_type,
             "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir,
             "-DPYTHON_EXECUTABLE=" + sys.executable,
@@ -80,7 +80,7 @@ setuptools.setup(
         "pybind11>=2.3.0",
         "pyside2",
         "qtpy",
-        "graphviz",
+        "graphviz<=0.17",
         "matplotlib",
         "scipy"
     ],
diff --git a/src/algorithm.h b/src/algorithm.hpp
similarity index 80%
rename from src/algorithm.h
rename to src/algorithm.hpp
index c86275d1c4ef09a525372d38a4c07a0beb11e8c4..4a609160d3bf9e9e0b065e105dd7ada3f759e8b9 100644
--- a/src/algorithm.h
+++ b/src/algorithm.hpp
@@ -1,5 +1,5 @@
-#ifndef ASIC_ALGORITHM_H
-#define ASIC_ALGORITHM_H
+#ifndef ASIC_ALGORITHM_HPP
+#define ASIC_ALGORITHM_HPP
 
 #include <cstddef>
 #include <iterator>
@@ -14,7 +14,8 @@ template <typename Reference>
 class arrow_proxy final {
 public:
 	template <typename Ref>
-	constexpr explicit arrow_proxy(Ref&& r) : m_r(std::forward<Ref>(r)) {}
+	constexpr explicit arrow_proxy(Ref&& r)
+		: m_r(std::forward<Ref>(r)) {}
 
 	Reference* operator->() {
 		return std::addressof(m_r);
@@ -25,7 +26,8 @@ private:
 };
 
 template <typename T>
-struct range_view final {
+class range_view final {
+public:
 	class iterator final {
 	public:
 		using difference_type = std::ptrdiff_t;
@@ -35,7 +37,8 @@ struct range_view final {
 		using iterator_category = std::random_access_iterator_tag;
 
 		constexpr iterator() noexcept = default;
-		constexpr explicit iterator(T value) noexcept : m_value(value) {}
+		constexpr explicit iterator(T value) noexcept
+			: m_value(value) {}
 
 		[[nodiscard]] constexpr bool operator==(iterator const& other) const noexcept {
 			return m_value == other.m_value;
@@ -124,21 +127,26 @@ struct range_view final {
 	using sentinel = iterator;
 
 	template <typename First, typename Last>
-	constexpr range_view(First&& first, Last&& last) noexcept : m_begin(std::forward<First>(first)), m_end(std::forward<Last>(last)) {}
+	constexpr range_view(First&& first, Last&& last) noexcept
+		: m_begin(std::forward<First>(first))
+		, m_end(std::forward<Last>(last)) {}
 
 	[[nodiscard]] constexpr iterator begin() const noexcept {
 		return m_begin;
 	}
+
 	[[nodiscard]] constexpr sentinel end() const noexcept {
 		return m_end;
 	}
 
+private:
 	iterator m_begin;
 	sentinel m_end;
 };
 
 template <typename Range, typename Iterator, typename Sentinel>
-struct enumerate_view final {
+class enumerate_view final {
+public:
 	using sentinel = Sentinel;
 
 	class iterator final {
@@ -152,7 +160,9 @@ struct enumerate_view final {
 
 		constexpr iterator() = default;
 
-		constexpr iterator(Iterator it, std::size_t index) : m_it(std::move(it)), m_index(index) {}
+		constexpr iterator(Iterator it, std::size_t index)
+			: m_it(std::move(it))
+			, m_index(index) {}
 
 		[[nodiscard]] constexpr bool operator==(iterator const& other) const {
 			return m_it == other.m_it;
@@ -203,35 +213,44 @@ struct enumerate_view final {
 		std::size_t m_index = 0;
 	};
 
-	constexpr iterator begin() const {
+	template <typename R>
+	constexpr explicit enumerate_view(R&& range)
+		: m_range(std::forward<R>(range)) {}
+
+	[[nodiscard]] constexpr iterator begin() const {
 		return iterator{std::begin(m_range), 0};
 	}
 
-	constexpr sentinel end() const {
+	[[nodiscard]] constexpr sentinel end() const {
 		return std::end(m_range);
 	}
 
+private:
 	Range m_range;
 };
 
 template <typename Range1, typename Range2, typename Iterator1, typename Iterator2, typename Sentinel1, typename Sentinel2>
-struct zip_view final {
+class zip_view final {
+public:
 	using sentinel = std::pair<Sentinel1, Sentinel2>;
 
 	class iterator final {
 	public:
 		using difference_type = std::common_type_t<typename std::iterator_traits<Iterator1>::difference_type,
-			typename std::iterator_traits<Iterator2>::difference_type>;
+												   typename std::iterator_traits<Iterator2>::difference_type>;
 		using value_type =
 			std::pair<typename std::iterator_traits<Iterator1>::value_type, typename std::iterator_traits<Iterator2>::value_type>;
 		using reference = std::pair<decltype(*std::declval<Iterator1 const>()), decltype(*std::declval<Iterator2 const>())>;
 		using pointer = arrow_proxy<reference>;
-		using iterator_category = std::common_type_t<typename std::iterator_traits<Iterator1>::iterator_category,
-			typename std::iterator_traits<Iterator2>::iterator_category, std::bidirectional_iterator_tag>;
+		using iterator_category =
+			std::common_type_t<typename std::iterator_traits<Iterator1>::iterator_category,
+							   typename std::iterator_traits<Iterator2>::iterator_category, std::bidirectional_iterator_tag>;
 
 		constexpr iterator() = default;
 
-		constexpr iterator(Iterator1 it1, Iterator2 it2) : m_it1(std::move(it1)), m_it2(std::move(it2)) {}
+		constexpr iterator(Iterator1 it1, Iterator2 it2)
+			: m_it1(std::move(it1))
+			, m_it2(std::move(it2)) {}
 
 		[[nodiscard]] constexpr bool operator==(iterator const& other) const {
 			return m_it1 == other.m_it1 && m_it2 == other.m_it2;
@@ -282,14 +301,20 @@ struct zip_view final {
 		Iterator2 m_it2;
 	};
 
-	constexpr iterator begin() const {
+	template <typename R1, typename R2>
+	constexpr zip_view(R1&& range1, R2&& range2)
+		: m_range1(std::forward<R1>(range1))
+		, m_range2(std::forward<R2>(range2)) {}
+
+	[[nodiscard]] constexpr iterator begin() const {
 		return iterator{std::begin(m_range1), std::begin(m_range2)};
 	}
 
-	constexpr sentinel end() const {
+	[[nodiscard]] constexpr sentinel end() const {
 		return sentinel{std::end(m_range1), std::end(m_range2)};
 	}
 
+private:
 	Range1 m_range1;
 	Range2 m_range2;
 };
@@ -307,19 +332,19 @@ template <typename Last, typename T = std::remove_cv_t<std::remove_reference_t<L
 }
 
 template <typename Range, typename Iterator = decltype(std::begin(std::declval<Range>())),
-	typename Sentinel = decltype(std::end(std::declval<Range>()))>
+		  typename Sentinel = decltype(std::end(std::declval<Range>()))>
 [[nodiscard]] constexpr auto enumerate(Range&& range) {
 	return detail::enumerate_view<Range, Iterator, Sentinel>{std::forward<Range>(range)};
 }
 
 template <typename Range1, typename Range2, typename Iterator1 = decltype(std::begin(std::declval<Range1>())),
-	typename Iterator2 = decltype(std::begin(std::declval<Range2>())), typename Sentinel1 = decltype(std::end(std::declval<Range1>())),
-	typename Sentinel2 = decltype(std::end(std::declval<Range2>()))>
+		  typename Iterator2 = decltype(std::begin(std::declval<Range2>())),
+		  typename Sentinel1 = decltype(std::end(std::declval<Range1>())), typename Sentinel2 = decltype(std::end(std::declval<Range2>()))>
 [[nodiscard]] constexpr auto zip(Range1&& range1, Range2&& range2) {
-	return detail::zip_view<Range1, Range2, Iterator1, Iterator2, Sentinel1, Sentinel2>{
-		std::forward<Range1>(range1), std::forward<Range2>(range2)};
+	return detail::zip_view<Range1, Range2, Iterator1, Iterator2, Sentinel1, Sentinel2>{std::forward<Range1>(range1),
+																						std::forward<Range2>(range2)};
 }
 
 } // namespace asic
 
-#endif // ASIC_ALGORITHM_H
\ No newline at end of file
+#endif // ASIC_ALGORITHM_HPP
diff --git a/src/debug.h b/src/debug.hpp
similarity index 91%
rename from src/debug.h
rename to src/debug.hpp
index a11aa057db644dbe2d29399398a1f48ca599876f..e1253790b21aedfb5c9d0205139dd74cf388f8fc 100644
--- a/src/debug.h
+++ b/src/debug.hpp
@@ -1,12 +1,26 @@
-#ifndef ASIC_DEBUG_H
-#define ASIC_DEBUG_H
+#ifndef ASIC_DEBUG_HPP
+#define ASIC_DEBUG_HPP
 
 #ifndef NDEBUG
+
+#ifndef ASIC_ENABLE_DEBUG_LOGGING
 #define ASIC_ENABLE_DEBUG_LOGGING 1
+#endif
+
+#ifndef ASIC_ENABLE_ASSERTS
 #define ASIC_ENABLE_ASSERTS 1
+#endif
+
 #else
+
+#ifndef ASIC_ENABLE_DEBUG_LOGGING
 #define ASIC_ENABLE_DEBUG_LOGGING 0
+#endif
+
+#ifndef ASIC_ENABLE_ASSERTS
 #define ASIC_ENABLE_ASSERTS 0
+#endif
+
 #endif // NDEBUG
 
 #if ASIC_ENABLE_DEBUG_LOGGING
@@ -19,11 +33,11 @@
 #endif // ASIC_ENABLE_DEBUG_LOGGING
 
 #if ASIC_ENABLE_ASSERTS
-#include <filesystem>
-#include <cstdlib>
 #include <cstdio>
-#include <string_view>
+#include <cstdlib>
+#include <filesystem>
 #include <fmt/format.h>
+#include <string_view>
 #endif // ASIC_ENABLE_ASSERTS
 
 namespace asic {
@@ -77,4 +91,4 @@ inline void check_assert(std::string_view file, int line, std::string_view condi
 #define ASIC_ASSERT(condition) ((void)0)
 #endif
 
-#endif // ASIC_DEBUG_H
\ No newline at end of file
+#endif // ASIC_DEBUG_HPP
diff --git a/src/main.cpp b/src/main.cpp
index f5c4be532aa47468592e2e2f008308d1724e41b8..8ac36c73b6261d6a7a0daaa9760ed037e9d5cb5a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,9 +1,9 @@
-#include "simulation.h"
-#include <pybind11/pybind11.h>
-
-namespace py = pybind11;
-
-PYBIND11_MODULE(_b_asic, module) {
-	module.doc() = "Better ASIC Toolbox Extension Module.";
-	asic::define_simulation_class(module);
-}
\ No newline at end of file
+#include "simulation.hpp"
+
+#define NOMINMAX
+#include <pybind11/pybind11.h>
+
+PYBIND11_MODULE(_b_asic, module) { // NOLINT
+	module.doc() = "Better ASIC Toolbox Extension Module.";
+	asic::define_simulation_class(module);
+}
diff --git a/src/number.h b/src/number.hpp
similarity index 58%
rename from src/number.h
rename to src/number.hpp
index 9cb5b42f53be4eb0cfcc86d00be65005147384e2..869a93f17965108d295022d0ddb2025ce7164ff8 100644
--- a/src/number.h
+++ b/src/number.hpp
@@ -1,6 +1,7 @@
-#ifndef ASIC_NUMBER_H
-#define ASIC_NUMBER_H
+#ifndef ASIC_NUMBER_HPP
+#define ASIC_NUMBER_HPP
 
+#define NOMINMAX
 #include <complex>
 #include <pybind11/complex.h>
 
@@ -10,4 +11,4 @@ using number = std::complex<double>;
 
 } // namespace asic
 
-#endif // ASIC_NUMBER_H
\ No newline at end of file
+#endif // ASIC_NUMBER_HPP
diff --git a/src/simulation.cpp b/src/simulation.cpp
index 33280f604be77614f7eadf1ad40d868177dd6e95..2f9ccd3df64dd223f695af62a60a550822525b2a 100644
--- a/src/simulation.cpp
+++ b/src/simulation.cpp
@@ -1,5 +1,6 @@
-#include "simulation.h"
-#include "simulation/simulation.h"
+#include "simulation.hpp"
+
+#include "simulation/simulation.hpp"
 
 namespace py = pybind11;
 
@@ -12,7 +13,7 @@ void define_simulation_class(pybind11::module& module) {
 			py::arg("sfg"),
 			"SFG Constructor.")
 
-		.def(py::init<py::handle, std::optional<std::vector<std::optional<input_provider_t>>>>(),
+		.def(py::init<py::handle, std::optional<std::vector<std::optional<input_provider_type>>>>(),
 			py::arg("sfg"), py::arg("input_providers"),
 			"SFG Constructor.")
 
@@ -58,4 +59,4 @@ void define_simulation_class(pybind11::module& module) {
 	// clang-format on
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/src/simulation.h b/src/simulation.hpp
similarity index 55%
rename from src/simulation.h
rename to src/simulation.hpp
index aefa3a4e92b861b9c7b795c7301f900dc54ace6f..5e7ab381e62b35a69aaf3f056e2808182c2e351d 100644
--- a/src/simulation.h
+++ b/src/simulation.hpp
@@ -1,6 +1,7 @@
-#ifndef ASIC_SIMULATION_H
-#define ASIC_SIMULATION_H
+#ifndef ASIC_SIMULATION_HPP
+#define ASIC_SIMULATION_HPP
 
+#define NOMINMAX
 #include <pybind11/pybind11.h>
 
 namespace asic {
@@ -9,4 +10,4 @@ void define_simulation_class(pybind11::module& module);
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_HPP
diff --git a/src/simulation/compile.cpp b/src/simulation/compile.cpp
index 7fa7ac6721c9af0f5b9decdf8c3f0dca653e47ec..4b0e7fc813efaea3fba291fad0135f753ff9403b 100644
--- a/src/simulation/compile.cpp
+++ b/src/simulation/compile.cpp
@@ -1,11 +1,11 @@
-#define NOMINMAX
-#include "compile.h"
+#include "compile.hpp"
 
-#include "../algorithm.h"
-#include "../debug.h"
-#include "../span.h"
-#include "format_code.h"
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "../span.hpp"
+#include "format_code.hpp"
 
+#define NOMINMAX
 #include <Python.h>
 #include <fmt/format.h>
 #include <limits>
@@ -25,7 +25,7 @@ namespace asic {
 }
 
 [[nodiscard]] static result_key key_of_output(py::handle op, std::size_t output_index, std::string_view prefix) {
-	auto const base = key_base(op, prefix);
+	auto base = key_base(op, prefix);
 	if (base.empty()) {
 		return fmt::to_string(output_index);
 	}
@@ -70,10 +70,10 @@ private:
 	using sfg_info_stack = std::vector<sfg_info>;
 	using delay_queue = std::vector<std::tuple<std::size_t, py::handle, std::string, sfg_info_stack>>;
 	using added_output_cache = std::unordered_set<PyObject const*>;
-	using added_result_cache = std::unordered_map<PyObject const*, result_index_t>;
+	using added_result_cache = std::unordered_map<PyObject const*, result_index_type>;
 	using added_custom_operation_cache = std::unordered_map<PyObject const*, std::size_t>;
 
-	static constexpr auto no_result_index = std::numeric_limits<result_index_t>::max();
+	static constexpr auto no_result_index = std::numeric_limits<result_index_type>::max();
 
 	void initialize_code(std::size_t input_count, std::size_t output_count) {
 		m_code.required_stack_size = 0;
@@ -101,7 +101,7 @@ private:
 	void resolve_invalid_result_indices() {
 		for (auto& instruction : m_code.instructions) {
 			if (instruction.result_index == no_result_index) {
-				instruction.result_index = static_cast<result_index_t>(m_code.result_keys.size());
+				instruction.result_index = static_cast<result_index_type>(m_code.result_keys.size());
 			}
 		}
 	}
@@ -128,7 +128,7 @@ private:
 		return new_sfg_stack;
 	}
 
-	instruction& add_instruction(instruction_type type, result_index_t result_index, std::ptrdiff_t stack_diff) {
+	instruction& add_instruction(instruction_type type, result_index_type result_index, std::ptrdiff_t stack_diff) {
 		m_stack_depth += stack_diff;
 		if (m_stack_depth < 0) {
 			throw py::value_error{"Detected input/output count mismatch in simulation SFG"};
@@ -142,8 +142,9 @@ private:
 		return instruction;
 	}
 
-	[[nodiscard]] std::optional<result_index_t> begin_operation_output(py::handle op, std::size_t output_index, std::string_view prefix) {
-		auto const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
+	[[nodiscard]] std::optional<result_index_type> begin_operation_output(py::handle op, std::size_t output_index,
+																		  std::string_view prefix) {
+		auto* const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
 		if (m_incomplete_outputs.count(pointer) != 0) {
 			// Make sure the output doesn't depend on its own value, unless it's a delay operation.
 			if (op.attr("type_name")().cast<std::string_view>() != "t") {
@@ -151,11 +152,11 @@ private:
 			}
 		}
 		// Try to add a new result.
-		auto const [it, inserted] = m_added_results.try_emplace(pointer, static_cast<result_index_t>(m_code.result_keys.size()));
+		auto const [it, inserted] = m_added_results.try_emplace(pointer, static_cast<result_index_type>(m_code.result_keys.size()));
 		if (inserted) {
-			if (m_code.result_keys.size() >= static_cast<std::size_t>(std::numeric_limits<result_index_t>::max())) {
+			if (m_code.result_keys.size() >= static_cast<std::size_t>(std::numeric_limits<result_index_type>::max())) {
 				throw py::value_error{fmt::format("Simulation SFG requires too many outputs to be stored (limit: {})",
-												  std::numeric_limits<result_index_t>::max())};
+												  std::numeric_limits<result_index_type>::max())};
 			}
 			m_code.result_keys.push_back(key_of_output(op, output_index, prefix));
 			m_incomplete_outputs.insert(pointer);
@@ -168,7 +169,7 @@ private:
 	}
 
 	void end_operation_output(py::handle op, std::size_t output_index) {
-		auto const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
+		auto* const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
 		[[maybe_unused]] auto const erased = m_incomplete_outputs.erase(pointer);
 		ASIC_ASSERT(erased == 1);
 	}
@@ -184,7 +185,7 @@ private:
 		return it->second;
 	}
 
-	[[nodiscard]] std::size_t add_delay_info(number initial_value, result_index_t result_index) {
+	[[nodiscard]] std::size_t add_delay_info(number initial_value, result_index_type result_index) {
 		auto const delay_index = m_code.delays.size();
 		auto& delay = m_code.delays.emplace_back();
 		delay.initial_value = initial_value;
@@ -209,14 +210,14 @@ private:
 		}
 	}
 
-	void add_unary_operation_output(py::handle op, result_index_t result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
+	void add_unary_operation_output(py::handle op, result_index_type result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
 									delay_queue& deferred_delays, instruction_type type) {
 		this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
 		this->add_instruction(type, result_index, 0);
 	}
 
-	void add_binary_operation_output(py::handle op, result_index_t result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
-									 delay_queue& deferred_delays, instruction_type type) {
+	void add_binary_operation_output(py::handle op, result_index_type result_index, std::string_view prefix,
+									 sfg_info_stack const& sfg_stack, delay_queue& deferred_delays, instruction_type type) {
 		this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
 		this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
 		this->add_instruction(type, result_index, -1);
@@ -299,10 +300,10 @@ private:
 		}
 	}
 
-	simulation_code m_code;
-	added_output_cache m_incomplete_outputs;
-	added_result_cache m_added_results;
-	added_custom_operation_cache m_added_custom_operations;
+	simulation_code m_code{};
+	added_output_cache m_incomplete_outputs{};
+	added_result_cache m_added_results{};
+	added_custom_operation_cache m_added_custom_operations{};
 	std::ptrdiff_t m_stack_depth = 0;
 };
 
@@ -310,4 +311,4 @@ simulation_code compile_simulation(pybind11::handle sfg) {
 	return compiler{}.compile(sfg);
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/src/simulation/compile.h b/src/simulation/compile.hpp
similarity index 69%
rename from src/simulation/compile.h
rename to src/simulation/compile.hpp
index 883f4c5832978ea1bfd33c767fc947c1efde718e..ee7fa3f18c90592428250da25a7259963ddaf2a3 100644
--- a/src/simulation/compile.h
+++ b/src/simulation/compile.hpp
@@ -1,8 +1,9 @@
-#ifndef ASIC_SIMULATION_COMPILE_H
-#define ASIC_SIMULATION_COMPILE_H
+#ifndef ASIC_SIMULATION_COMPILE_HPP
+#define ASIC_SIMULATION_COMPILE_HPP
 
-#include "instruction.h"
+#include "instruction.hpp"
 
+#define NOMINMAX
 #include <cstddef>
 #include <pybind11/pybind11.h>
 #include <string>
@@ -15,47 +16,47 @@ using result_key = std::string;
 struct simulation_code final {
 	struct custom_operation final {
 		// Python function used to evaluate the custom operation.
-		pybind11::object evaluate_output;
+		pybind11::object evaluate_output{};
 		// Number of inputs that the custom operation takes.
-		std::size_t input_count;
+		std::size_t input_count = 0;
 		// Number of outputs that the custom operation gives.
-		std::size_t output_count;
+		std::size_t output_count = 0;
 	};
 
 	struct custom_source final {
 		// Index into custom_operations where the custom_operation corresponding to this custom_source is located.
-		std::size_t custom_operation_index;
+		std::size_t custom_operation_index = 0;
 		// Output index of the custom_operation that this source gets it value from.
-		std::size_t output_index;
+		std::size_t output_index = 0;
 	};
 
 	struct delay_info final {
 		// Initial value to set at the start of the simulation.
-		number initial_value;
+		number initial_value{};
 		// The result index where the current value should be stored at the start of each iteration.
-		result_index_t result_index;
+		result_index_type result_index = 0;
 	};
 
 	// Instructions to execute for one full iteration of the simulation.
-	std::vector<instruction> instructions;
+	std::vector<instruction> instructions{};
 	// Custom operations used by the simulation.
-	std::vector<custom_operation> custom_operations;
+	std::vector<custom_operation> custom_operations{};
 	// Signal sources that use custom operations.
-	std::vector<custom_source> custom_sources;
+	std::vector<custom_source> custom_sources{};
 	// Info about the delay operations used in the simulation.
-	std::vector<delay_info> delays;
+	std::vector<delay_info> delays{};
 	// Keys for each result produced by the simulation. The index of the key matches the index of the result in the simulation state.
-	std::vector<result_key> result_keys;
+	std::vector<result_key> result_keys{};
 	// Number of values expected as input to the simulation.
-	std::size_t input_count;
+	std::size_t input_count = 0;
 	// Number of values given as output from the simulation. This will be the number of values left on the stack after a full iteration of the simulation has been run.
-	std::size_t output_count;
+	std::size_t output_count = 0;
 	// Maximum number of values that need to be able to fit on the stack in order to run a full iteration of the simulation.
-	std::size_t required_stack_size;
+	std::size_t required_stack_size = 0;
 };
 
 [[nodiscard]] simulation_code compile_simulation(pybind11::handle sfg);
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_COMPILE_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_COMPILE_HPP
diff --git a/src/simulation/format_code.h b/src/simulation/format_code.hpp
similarity index 94%
rename from src/simulation/format_code.h
rename to src/simulation/format_code.hpp
index 5ebbb95d1f11eb18b915dbab9fbccbb82d83304c..6b286d0e954289065882c8f795d9bdeb4dea3ed2 100644
--- a/src/simulation/format_code.h
+++ b/src/simulation/format_code.hpp
@@ -1,11 +1,11 @@
-#ifndef ASIC_SIMULATION_FORMAT_CODE_H
-#define ASIC_SIMULATION_FORMAT_CODE_H
+#ifndef ASIC_SIMULATION_FORMAT_CODE_HPP
+#define ASIC_SIMULATION_FORMAT_CODE_HPP
 
-#include "../algorithm.h"
-#include "../debug.h"
-#include "../number.h"
-#include "compile.h"
-#include "instruction.h"
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "../number.hpp"
+#include "compile.hpp"
+#include "instruction.hpp"
 
 #include <fmt/format.h>
 #include <string>
@@ -47,8 +47,8 @@ namespace asic {
 }
 
 [[nodiscard]] inline std::string format_compiled_simulation_code_instruction(instruction const& instruction) {
+	// clang-format off
 	switch (instruction.type) {
-		// clang-format off
 		case instruction_type::push_input:              return fmt::format("push_input inputs[{}]", instruction.index);
 		case instruction_type::push_result:             return fmt::format("push_result results[{}]", instruction.index);
 		case instruction_type::push_delay:              return fmt::format("push_delay delays[{}]", instruction.index);
@@ -67,8 +67,8 @@ namespace asic {
 		case instruction_type::update_delay:            return fmt::format("update_delay delays[{}]", instruction.index);
 		case instruction_type::custom:                  return fmt::format("custom custom_sources[{}]", instruction.index);
 		case instruction_type::forward_value:           return "forward_value";
-		// clang-format on
 	}
+	// clang-format on
 	return std::string{};
 }
 
@@ -126,4 +126,4 @@ namespace asic {
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_FORMAT_CODE
\ No newline at end of file
+#endif // ASIC_SIMULATION_FORMAT_CODE_HPP
diff --git a/src/simulation/instruction.h b/src/simulation/instruction.hpp
similarity index 62%
rename from src/simulation/instruction.h
rename to src/simulation/instruction.hpp
index d650c651394a243c52eee7e5ad2fe463f96bdad7..22d0464d1e7cc19bf47cdd2a01fdd807ce53de6e 100644
--- a/src/simulation/instruction.h
+++ b/src/simulation/instruction.hpp
@@ -1,7 +1,7 @@
-#ifndef ASIC_SIMULATION_INSTRUCTION_H
-#define ASIC_SIMULATION_INSTRUCTION_H
+#ifndef ASIC_SIMULATION_INSTRUCTION_HPP
+#define ASIC_SIMULATION_INSTRUCTION_HPP
 
-#include "../number.h"
+#include "../number.hpp"
 
 #include <cstddef>
 #include <cstdint>
@@ -15,12 +15,12 @@ enum class instruction_type : std::uint8_t {
 	push_delay,              // push(delays[index])
 	push_constant,           // push(value)
 	truncate,                // push(trunc(pop(), bit_mask))
-	addition,                // push(pop() + pop())
-	subtraction,             // push(pop() - pop())
-	multiplication,          // push(pop() * pop())
-	division,                // push(pop() / pop())
-	min,                     // push(min(pop(), pop()))
-	max,                     // push(max(pop(), pop()))
+	addition,                // rhs=pop(), lhs=pop(), push(lhs + rhs)
+	subtraction,             // rhs=pop(), lhs=pop(), push(lhs - rhs)
+	multiplication,          // rhs=pop(), lhs=pop(), push(lhs * rhs)
+	division,                // rhs=pop(), lhs=pop(), push(lhs / rhs)
+	min,                     // rhs=pop(), lhs=pop(), push(min(lhs, rhs))
+	max,                     // rhs=pop(), lhs=pop(), push(max(lhs, rhs))
 	square_root,             // push(sqrt(pop()))
 	complex_conjugate,       // push(conj(pop()))
 	absolute,                // push(abs(pop()))
@@ -30,13 +30,11 @@ enum class instruction_type : std::uint8_t {
 	forward_value            // Forward the current value on the stack (push(pop()), i.e. do nothing).
 };
 
-using result_index_t = std::uint16_t;
+using result_index_type = std::uint16_t;
 
 struct instruction final {
-	constexpr instruction() noexcept
-		: index(0)
-		, result_index(0)
-		, type(instruction_type::forward_value) {}
+	constexpr instruction() noexcept // NOLINT(cppcoreguidelines-pro-type-member-init)
+		: index(0) {}
 
 	union {
 		// Index used by push_input, push_result, delay and custom.
@@ -47,11 +45,11 @@ struct instruction final {
 		number value;
 	};
 	// Index into where the result of the instruction will be stored. If the result should be ignored, this index will be one past the last valid result index.
-	result_index_t result_index;
+	result_index_type result_index = 0;
 	// Specifies what kind of operation the instruction should execute.
-	instruction_type type;
+	instruction_type type = instruction_type::forward_value;
 };
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_INSTRUCTION_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_INSTRUCTION_HPP
diff --git a/src/simulation/run.cpp b/src/simulation/run.cpp
index c14fa192bf0fd52a6b661aa4fb392b78859c382f..da307533b3791e0d2e89cae99a61d107ea5a2301 100644
--- a/src/simulation/run.cpp
+++ b/src/simulation/run.cpp
@@ -1,10 +1,10 @@
-#define NOMINMAX
-#include "run.h"
+#include "run.hpp"
 
-#include "../algorithm.h"
-#include "../debug.h"
-#include "format_code.h"
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "format_code.hpp"
 
+#define NOMINMAX
 #include <algorithm>
 #include <complex>
 #include <cstddef>
@@ -54,7 +54,7 @@ simulation_state run_simulation(simulation_code const& code, span<number const>
 
 	// Setup stack.
 	state.stack.resize(code.required_stack_size);
-	auto stack_pointer = state.stack.data();
+	auto* stack_pointer = state.stack.data();
 
 	// Utility functions to make the stack manipulation code below more readable.
 	// Should hopefully be inlined by the compiler.
@@ -97,21 +97,33 @@ simulation_state run_simulation(simulation_code const& code, span<number const>
 					push(truncate_value(pop(), instruction.bit_mask));
 				}
 				break;
-			case instruction_type::addition:
-				push(pop() + pop());
+			case instruction_type::addition: {
+				auto const rhs = pop();
+				auto const lhs = pop();
+				push(lhs + rhs);
 				break;
-			case instruction_type::subtraction:
-				push(pop() - pop());
+			}
+			case instruction_type::subtraction: {
+				auto const rhs = pop();
+				auto const lhs = pop();
+				push(lhs - rhs);
 				break;
-			case instruction_type::multiplication:
-				push(pop() * pop());
+			}
+			case instruction_type::multiplication: {
+				auto const rhs = pop();
+				auto const lhs = pop();
+				push(lhs * rhs);
 				break;
-			case instruction_type::division:
-				push(pop() / pop());
+			}
+			case instruction_type::division: {
+				auto const rhs = pop();
+				auto const lhs = pop();
+				push(lhs / rhs);
 				break;
+			}
 			case instruction_type::min: {
-				auto const lhs = pop();
 				auto const rhs = pop();
+				auto const lhs = pop();
 				if (lhs.imag() != 0 || rhs.imag() != 0) {
 					throw std::runtime_error{"Min does not support complex numbers."};
 				}
@@ -119,8 +131,8 @@ simulation_state run_simulation(simulation_code const& code, span<number const>
 				break;
 			}
 			case instruction_type::max: {
-				auto const lhs = pop();
 				auto const rhs = pop();
+				auto const lhs = pop();
 				if (lhs.imag() != 0 || rhs.imag() != 0) {
 					throw std::runtime_error{"Max does not support complex numbers."};
 				}
@@ -173,4 +185,4 @@ simulation_state run_simulation(simulation_code const& code, span<number const>
 	return state;
 }
 
-} // namespace asic
\ No newline at end of file
+} // namespace asic
diff --git a/src/simulation/run.h b/src/simulation/run.hpp
similarity index 55%
rename from src/simulation/run.h
rename to src/simulation/run.hpp
index 2174c571ef59f3e12236471e3e064f2619c38a60..32c56b3e764c01080bbf28b93ebb4e1fec4d9556 100644
--- a/src/simulation/run.h
+++ b/src/simulation/run.hpp
@@ -1,9 +1,9 @@
-#ifndef ASIC_SIMULATION_RUN_H
-#define ASIC_SIMULATION_RUN_H
+#ifndef ASIC_SIMULATION_RUN_HPP
+#define ASIC_SIMULATION_RUN_HPP
 
-#include "../number.h"
-#include "../span.h"
-#include "compile.h"
+#include "../number.hpp"
+#include "../span.hpp"
+#include "compile.hpp"
 
 #include <cstdint>
 #include <vector>
@@ -11,8 +11,8 @@
 namespace asic {
 
 struct simulation_state final {
-	std::vector<number> stack;
-	std::vector<number> results;
+	std::vector<number> stack{};
+	std::vector<number> results{};
 };
 
 simulation_state run_simulation(simulation_code const& code, span<number const> inputs, span<number> delays,
@@ -20,4 +20,4 @@ simulation_state run_simulation(simulation_code const& code, span<number const>
 
 } // namespace asic
 
-#endif // ASIC_SIMULATION_RUN_H
\ No newline at end of file
+#endif // ASIC_SIMULATION_RUN_HPP
diff --git a/src/simulation/simulation.cpp b/src/simulation/simulation.cpp
index 3af24c10e62bfe09590a05153abd25468d6bee4c..540505b40d2b09fa301c030ca374102b0d63ba02 100644
--- a/src/simulation/simulation.cpp
+++ b/src/simulation/simulation.cpp
@@ -1,11 +1,11 @@
-#define NOMINMAX
-#include "simulation.h"
+#include "simulation.hpp"
 
-#include "../algorithm.h"
-#include "../debug.h"
-#include "compile.h"
-#include "run.h"
+#include "../algorithm.hpp"
+#include "../debug.hpp"
+#include "compile.hpp"
+#include "run.hpp"
 
+#define NOMINMAX
 #include <fmt/format.h>
 #include <limits>
 #include <pybind11/numpy.h>
@@ -15,9 +15,9 @@ namespace py = pybind11;
 
 namespace asic {
 
-simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers)
+simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers)
 	: m_code(compile_simulation(sfg))
-	, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_t) -> number { return number{}; }) {
+	, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_type) -> number { return number{}; }) {
 	m_delays.reserve(m_code.delays.size());
 	for (auto const& delay : m_code.delays) {
 		m_delays.push_back(delay.initial_value);
@@ -27,29 +27,30 @@ simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::opti
 	}
 }
 
-void simulation::set_input(std::size_t index, input_provider_t input_provider) {
+void simulation::set_input(std::size_t index, input_provider_type input_provider) {
 	if (index >= m_input_functions.size()) {
 		throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
 	}
-	if (auto* const callable = std::get_if<input_function_t>(&input_provider)) {
+	if (auto* const callable = std::get_if<input_function_type>(&input_provider)) {
 		m_input_functions[index] = std::move(*callable);
 	} else if (auto* const numeric = std::get_if<number>(&input_provider)) {
-		m_input_functions[index] = [value = *numeric](iteration_t) -> number {
+		m_input_functions[index] = [value = *numeric](iteration_type) -> number {
 			return value;
 		};
 	} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
 		if (!m_input_length) {
-			m_input_length = static_cast<iteration_t>(list->size());
-		} else if (*m_input_length != static_cast<iteration_t>(list->size())) {
+			m_input_length = static_cast<iteration_type>(list->size());
+		} else if (*m_input_length != static_cast<iteration_type>(list->size())) {
 			throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
 		}
-		m_input_functions[index] = [values = std::move(*list)](iteration_t n) -> number {
+		m_input_functions[index] = [values = std::move(*list)](iteration_type n) -> number {
 			return values.at(n);
 		};
 	}
 }
 
-void simulation::set_inputs(std::vector<std::optional<input_provider_t>> input_providers) {
+void simulation::set_inputs(
+	std::vector<std::optional<input_provider_type>> input_providers) { // NOLINT(performance-unnecessary-value-param)
 	if (input_providers.size() != m_input_functions.size()) {
 		throw py::value_error{fmt::format(
 			"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
@@ -65,7 +66,7 @@ std::vector<number> simulation::step(bool save_results, std::optional<std::uint8
 	return this->run_for(1, save_results, bits_override, truncate);
 }
 
-std::vector<number> simulation::run_until(iteration_t iteration, bool save_results, std::optional<std::uint8_t> bits_override,
+std::vector<number> simulation::run_until(iteration_type iteration, bool save_results, std::optional<std::uint8_t> bits_override,
 										  bool truncate) {
 	auto result = std::vector<number>{};
 	while (m_iteration < iteration) {
@@ -84,9 +85,9 @@ std::vector<number> simulation::run_until(iteration_t iteration, bool save_resul
 	return result;
 }
 
-std::vector<number> simulation::run_for(iteration_t iterations, bool save_results, std::optional<std::uint8_t> bits_override,
+std::vector<number> simulation::run_for(iteration_type iterations, bool save_results, std::optional<std::uint8_t> bits_override,
 										bool truncate) {
-	if (iterations > std::numeric_limits<iteration_t>::max() - m_iteration) {
+	if (iterations > std::numeric_limits<iteration_type>::max() - m_iteration) {
 		throw py::value_error("Simulation iteration type overflow!");
 	}
 	return this->run_until(m_iteration + iterations, save_results, bits_override, truncate);
@@ -99,7 +100,7 @@ std::vector<number> simulation::run(bool save_results, std::optional<std::uint8_
 	throw py::index_error{"Tried to run unlimited simulation"};
 }
 
-iteration_t simulation::iteration() const noexcept {
+iteration_type simulation::iteration() const noexcept {
 	return m_iteration;
 }
 
diff --git a/src/simulation/simulation.h b/src/simulation/simulation.h
deleted file mode 100644
index c1a36cbc93492494af14a198c970a6a534477794..0000000000000000000000000000000000000000
--- a/src/simulation/simulation.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef ASIC_SIMULATION_DOD_H
-#define ASIC_SIMULATION_DOD_H
-
-#include "../number.h"
-#include "compile.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <functional>
-#include <optional>
-#include <pybind11/functional.h>
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <variant>
-#include <vector>
-
-namespace asic {
-
-using iteration_t = std::uint32_t;
-using input_function_t = std::function<number(iteration_t)>;
-using input_provider_t = std::variant<number, std::vector<number>, input_function_t>;
-
-class simulation final {
-public:
-	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers = std::nullopt);
-
-	void set_input(std::size_t index, input_provider_t input_provider);
-	void set_inputs(std::vector<std::optional<input_provider_t>> input_providers);
-
-	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
-	[[nodiscard]] std::vector<number> run_until(iteration_t iteration, bool save_results, std::optional<std::uint8_t> bits_override,
-												bool truncate);
-	[[nodiscard]] std::vector<number> run_for(iteration_t iterations, bool save_results, std::optional<std::uint8_t> bits_override,
-											  bool truncate);
-	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
-
-	[[nodiscard]] iteration_t iteration() const noexcept;
-	[[nodiscard]] pybind11::dict results() const noexcept;
-
-	void clear_results() noexcept;
-	void clear_state() noexcept;
-
-private:
-	simulation_code m_code;
-	std::vector<number> m_delays;
-	std::vector<input_function_t> m_input_functions;
-	std::optional<iteration_t> m_input_length;
-	iteration_t m_iteration = 0;
-	std::vector<std::vector<number>> m_results;
-};
-
-} // namespace asic
-
-#endif // ASIC_SIMULATION_DOD_H
\ No newline at end of file
diff --git a/src/simulation/simulation.hpp b/src/simulation/simulation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e5624cd27a801383a07cd64b54ec5c04028ea5eb
--- /dev/null
+++ b/src/simulation/simulation.hpp
@@ -0,0 +1,55 @@
+#ifndef ASIC_SIMULATION_DOD_HPP
+#define ASIC_SIMULATION_DOD_HPP
+
+#include "../number.hpp"
+#include "compile.hpp"
+
+#define NOMINMAX
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <pybind11/functional.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <variant>
+#include <vector>
+
+namespace asic {
+
+using iteration_type = std::uint32_t;
+using input_function_type = std::function<number(iteration_type)>;
+using input_provider_type = std::variant<number, std::vector<number>, input_function_type>;
+
+class simulation final {
+public:
+	simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers = std::nullopt);
+
+	void set_input(std::size_t index, input_provider_type input_provider);
+	void set_inputs(std::vector<std::optional<input_provider_type>> input_providers);
+
+	[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
+	[[nodiscard]] std::vector<number> run_until(iteration_type iteration, bool save_results, std::optional<std::uint8_t> bits_override,
+												bool truncate);
+	[[nodiscard]] std::vector<number> run_for(iteration_type iterations, bool save_results, std::optional<std::uint8_t> bits_override,
+											  bool truncate);
+	[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::uint8_t> bits_override, bool truncate);
+
+	[[nodiscard]] iteration_type iteration() const noexcept;
+	[[nodiscard]] pybind11::dict results() const noexcept;
+
+	void clear_results() noexcept;
+	void clear_state() noexcept;
+
+private:
+	simulation_code m_code;
+	std::vector<input_function_type> m_input_functions;
+	std::vector<number> m_delays{};
+	std::optional<iteration_type> m_input_length{};
+	iteration_type m_iteration = 0;
+	std::vector<std::vector<number>> m_results{};
+};
+
+} // namespace asic
+
+#endif // ASIC_SIMULATION_DOD_HPP
diff --git a/src/span.h b/src/span.hpp
similarity index 58%
rename from src/span.h
rename to src/span.hpp
index 2ad454e13e3978c74355ae3ca0f955fad1bb9753..3ccc34eaa9bd485d84808ff812d52e74a98c53f4 100644
--- a/src/span.h
+++ b/src/span.hpp
@@ -1,14 +1,14 @@
-#ifndef ASIC_SPAN_H
-#define ASIC_SPAN_H
+#ifndef ASIC_SPAN_HPP
+#define ASIC_SPAN_HPP
 
+#include <algorithm>
+#include <array>
+#include <cassert>
 #include <cstddef>
-#include <type_traits>
-#include <utility>
 #include <iterator>
 #include <limits>
-#include <array>
-#include <algorithm>
-#include <cassert>
+#include <type_traits>
+#include <utility>
 
 namespace asic {
 
@@ -51,28 +51,38 @@ template <std::size_t From, std::size_t To>
 constexpr auto is_size_convertible_v = is_size_convertible<From, To>::value;
 
 template <typename From, typename To>
-struct is_element_type_convertible : std::bool_constant<std::is_convertible_v<From(*)[], To(*)[]>> {};
+struct is_element_type_convertible : std::bool_constant<std::is_convertible_v<From (*)[], To (*)[]>> {};
 
 template <typename From, typename To>
 constexpr auto is_element_type_convertible_v = is_element_type_convertible<From, To>::value;
 
 template <typename T, std::size_t Size>
 struct span_base {
-	using element_type	= T;
-	using pointer		= element_type*;
-	using size_type		= std::size_t;
+	using element_type = T;
+	using pointer = element_type*;
+	using size_type = std::size_t;
 
 	constexpr span_base() noexcept = default;
-	constexpr span_base(pointer data, [[maybe_unused]] size_type size) : m_data(data) { assert(size == Size); }
+
+	constexpr span_base(pointer data, [[maybe_unused]] size_type size)
+		: m_data(data) {
+		assert(size == Size);
+	}
 
 	template <size_type N>
-	constexpr span_base(span_base<T, N> other) : m_data(other.data()) {
+	constexpr span_base(span_base<T, N> other)
+		: m_data(other.data()) {
 		static_assert(N == Size || N == dynamic_size);
 		assert(other.size() == Size);
 	}
 
-	[[nodiscard]] constexpr pointer data() const noexcept	{ return m_data; }
-	[[nodiscard]] constexpr size_type size() const noexcept	{ return Size; }
+	[[nodiscard]] constexpr pointer data() const noexcept {
+		return m_data;
+	}
+
+	[[nodiscard]] constexpr size_type size() const noexcept {
+		return Size;
+	}
 
 private:
 	pointer m_data = nullptr;
@@ -80,34 +90,37 @@ private:
 
 template <typename T>
 struct span_base<T, dynamic_size> {
-	using element_type	= T;
-	using pointer		= element_type*;
-	using size_type		= std::size_t;
+	using element_type = T;
+	using pointer = element_type*;
+	using size_type = std::size_t;
 
 	constexpr span_base() noexcept = default;
-	constexpr span_base(pointer data, size_type size) : m_data(data), m_size(size) {}
+
+	constexpr span_base(pointer data, size_type size)
+		: m_data(data)
+		, m_size(size) {}
 
 	template <size_type N>
-	explicit constexpr span_base(span_base<T, N> other) : m_data(other.data()), m_size(other.size()) {}
+	explicit constexpr span_base(span_base<T, N> other)
+		: m_data(other.data())
+		, m_size(other.size()) {}
+
+	[[nodiscard]] constexpr pointer data() const noexcept {
+		return m_data;
+	}
 
-	[[nodiscard]] constexpr pointer data() const noexcept	{ return m_data; }
-	[[nodiscard]] constexpr size_type size() const noexcept	{ return m_size; }
+	[[nodiscard]] constexpr size_type size() const noexcept {
+		return m_size;
+	}
 
 private:
-	pointer		m_data = nullptr;
-	size_type	m_size = 0;
+	pointer m_data = nullptr;
+	size_type m_size = 0;
 };
 
 template <typename T, std::size_t Size, std::size_t Offset, std::size_t N>
 struct subspan_type {
-	using type = span<
-		T,
-		(N != dynamic_size) ?
-			N :
-			(Size != dynamic_size) ?
-				Size - Offset :
-				Size
-	>;
+	using type = span<T, (N != dynamic_size) ? N : (Size != dynamic_size) ? Size - Offset : Size>;
 };
 
 template <typename T, std::size_t Size, std::size_t Offset, std::size_t Count>
@@ -116,31 +129,30 @@ using subspan_type_t = typename subspan_type<T, Size, Offset, Count>::type;
 } // namespace detail
 
 template <typename T, std::size_t Size>
-class span final : public detail::span_base<T, Size> {
+class span final : public detail::span_base<T, Size> { // NOLINT(cppcoreguidelines-special-member-functions)
 public:
-	using element_type				= typename detail::span_base<T, Size>::element_type;
-	using pointer					= typename detail::span_base<T, Size>::pointer;
-	using size_type					= typename detail::span_base<T, Size>::size_type;
-	using value_type				= std::remove_cv_t<element_type>;
-	using reference					= element_type&;
-	using iterator					= element_type*;
-	using const_iterator			= const element_type*;
-	using reverse_iterator			= std::reverse_iterator<iterator>;
-	using const_reverse_iterator	= std::reverse_iterator<const_iterator>;
+	using element_type = typename detail::span_base<T, Size>::element_type;
+	using pointer = typename detail::span_base<T, Size>::pointer;
+	using size_type = typename detail::span_base<T, Size>::size_type;
+	using value_type = std::remove_cv_t<element_type>;
+	using reference = element_type&;
+	using iterator = element_type*;
+	using const_iterator = const element_type*;
+	using reverse_iterator = std::reverse_iterator<iterator>;
+	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
 
 	// Default constructor.
 	constexpr span() noexcept = default;
 
 	// Construct from pointer, size.
-	constexpr span(pointer data, size_type size) : detail::span_base<T, Size>(data, size) {}
+	constexpr span(pointer data, size_type size)
+		: detail::span_base<T, Size>(data, size) {}
 
 	// Copy constructor.
-	template <
-		typename U, std::size_t N,
-		typename = std::enable_if_t<detail::is_size_convertible_v<N, Size>>,
-		typename = std::enable_if_t<detail::is_element_type_convertible_v<U, T>>
-	>
-	constexpr span(span<U, N> const& other) : span(other.data(), other.size()) {}
+	template <typename U, std::size_t N, typename = std::enable_if_t<detail::is_size_convertible_v<N, Size>>,
+			  typename = std::enable_if_t<detail::is_element_type_convertible_v<U, T>>>
+	constexpr span(span<U, N> const& other)
+		: span(other.data(), other.size()) {}
 
 	// Copy assignment.
 	constexpr span& operator=(span const&) noexcept = default;
@@ -149,75 +161,111 @@ public:
 	~span() = default;
 
 	// Construct from begin, end.
-	constexpr span(pointer begin, pointer end) : span(begin, end - begin) {}
+	constexpr span(pointer begin, pointer end)
+		: span(begin, end - begin) {}
 
 	// Construct from C array.
 	template <std::size_t N>
-	constexpr span(element_type(&arr)[N]) noexcept : span(std::data(arr), N) {}
+	constexpr span(element_type (&arr)[N]) noexcept
+		: span(std::data(arr), N) {}
 
 	// Construct from std::array.
-	template <
-		std::size_t N,
-		typename = std::enable_if_t<N != 0>
-	>
-	constexpr span(std::array<value_type, N>& arr) noexcept : span(std::data(arr), N) {}
+	template <std::size_t N, typename = std::enable_if_t<N != 0>>
+	constexpr span(std::array<value_type, N>& arr) noexcept
+		: span(std::data(arr), N) {}
 
 	// Construct from empty std::array.
-	constexpr span(std::array<value_type, 0>&) noexcept : span() {}
+	constexpr span(std::array<value_type, 0>&) noexcept
+		: span() {}
 
 	// Construct from const std::array.
-	template <
-		std::size_t N,
-		typename = std::enable_if_t<N != 0>
-	>
-	constexpr span(std::array<value_type, N> const& arr) noexcept : span(std::data(arr), N) {}
+	template <std::size_t N, typename = std::enable_if_t<N != 0>>
+	constexpr span(std::array<value_type, N> const& arr) noexcept
+		: span(std::data(arr), N) {}
 
 	// Construct from empty const std::array.
-	constexpr span(std::array<value_type, 0> const&) noexcept : span() {}
+	constexpr span(std::array<value_type, 0> const&) noexcept
+		: span() {}
 
 	// Construct from other container.
 	template <
-		typename Container,
-		typename = std::enable_if_t<!detail::is_span_v<Container>>,
-		typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
-		typename = decltype(std::data(std::declval<Container>())),
+		typename Container, typename = std::enable_if_t<!detail::is_span_v<Container>>,
+		typename = std::enable_if_t<!detail::is_std_array_v<Container>>, typename = decltype(std::data(std::declval<Container>())),
 		typename = decltype(std::size(std::declval<Container>())),
 		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
-		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>
-	>
-	constexpr span(Container& container) : span(std::data(container), std::size(container)) {}
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>>
+	constexpr span(Container& container)
+		: span(std::data(container), std::size(container)) {}
 
 	// Construct from other const container.
 	template <
-		typename Container,
-		typename Element = element_type,
-		typename = std::enable_if_t<std::is_const_v<Element>>,
-		typename = std::enable_if_t<!detail::is_span_v<Container>>,
-		typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
-		typename = decltype(std::data(std::declval<Container>())),
-		typename = decltype(std::size(std::declval<Container>())),
+		typename Container, typename Element = element_type, typename = std::enable_if_t<std::is_const_v<Element>>,
+		typename = std::enable_if_t<!detail::is_span_v<Container>>, typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
+		typename = decltype(std::data(std::declval<Container>())), typename = decltype(std::size(std::declval<Container>())),
 		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
-		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>
-	>
-	constexpr span(Container const& container) : span(std::data(container), std::size(container)) {}
+		typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>>
+	constexpr span(Container const& container)
+		: span(std::data(container), std::size(container)) {}
 
-	[[nodiscard]] constexpr iterator begin() const noexcept						{ return this->data(); }
-	[[nodiscard]] constexpr const_iterator cbegin() const noexcept				{ return this->data(); }
-	[[nodiscard]] constexpr iterator end() const noexcept						{ return this->data() + this->size(); }
-	[[nodiscard]] constexpr const_iterator cend() const noexcept				{ return this->data() + this->size(); }
-	[[nodiscard]] constexpr reverse_iterator rbegin() const noexcept			{ return std::make_reverse_iterator(this->end()); }
-	[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept		{ return std::make_reverse_iterator(this->cend()); }
-	[[nodiscard]] constexpr reverse_iterator rend() const noexcept				{ return std::make_reverse_iterator(this->begin()); }
-	[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept		{ return std::make_reverse_iterator(this->cbegin()); }
+	[[nodiscard]] constexpr iterator begin() const noexcept {
+		return this->data();
+	}
+
+	[[nodiscard]] constexpr const_iterator cbegin() const noexcept {
+		return this->data();
+	}
+
+	[[nodiscard]] constexpr iterator end() const noexcept {
+		return this->data() + this->size();
+	}
+
+	[[nodiscard]] constexpr const_iterator cend() const noexcept {
+		return this->data() + this->size();
+	}
+
+	[[nodiscard]] constexpr reverse_iterator rbegin() const noexcept {
+		return std::make_reverse_iterator(this->end());
+	}
+
+	[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept {
+		return std::make_reverse_iterator(this->cend());
+	}
+
+	[[nodiscard]] constexpr reverse_iterator rend() const noexcept {
+		return std::make_reverse_iterator(this->begin());
+	}
+
+	[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept {
+		return std::make_reverse_iterator(this->cbegin());
+	}
+
+	[[nodiscard]] constexpr reference operator[](size_type i) const noexcept {
+		assert(i < this->size());
+		return this->data()[i];
+	}
+
+	[[nodiscard]] constexpr reference operator()(size_type i) const noexcept {
+		assert(i < this->size());
+		return this->data()[i];
+	}
+
+	[[nodiscard]] constexpr size_type size_bytes() const noexcept {
+		return this->size() * sizeof(element_type);
+	}
 
-	[[nodiscard]] constexpr reference operator[](size_type i) const noexcept	{ assert(i < this->size()); return this->data()[i]; }
-	[[nodiscard]] constexpr reference operator()(size_type i) const noexcept	{ assert(i < this->size()); return this->data()[i]; }
+	[[nodiscard]] constexpr bool empty() const noexcept {
+		return this->size() == 0;
+	}
 
-	[[nodiscard]] constexpr size_type size_bytes() const noexcept				{ return this->size() * sizeof(element_type); }
-	[[nodiscard]] constexpr bool empty() const noexcept							{ return this->size() == 0; }
+	[[nodiscard]] constexpr reference front() const noexcept {
+		assert(!this->empty());
+		return this->data()[0];
+	}
 
-	[[nodiscard]] constexpr reference front() const noexcept					{ assert(!this->empty()); return this->data()[0]; }
-	[[nodiscard]] constexpr reference back() const noexcept						{ assert(!this->empty()); return this->data()[this->size() - 1]; }
+	[[nodiscard]] constexpr reference back() const noexcept {
+		assert(!this->empty());
+		return this->data()[this->size() - 1];
+	}
 
 	template <std::size_t N>
 	[[nodiscard]] constexpr span<T, N> first() const {
@@ -239,7 +287,7 @@ public:
 
 	[[nodiscard]] constexpr span<T, dynamic_size> first(size_type n) const {
 		assert(n <= this->size());
-		return { this->data(), n };
+		return {this->data(), n};
 	}
 
 	[[nodiscard]] constexpr span<T, dynamic_size> last(size_type n) const {
@@ -250,7 +298,7 @@ public:
 		if constexpr (Size == dynamic_size) {
 			assert(offset <= this->size());
 			if (n == dynamic_size) {
-				return { this->data() + offset, this->size() - offset };
+				return {this->data() + offset, this->size() - offset};
 			}
 			assert(n <= this->size());
 			assert(offset + n <= this->size());
@@ -298,7 +346,7 @@ template <typename Container>
 span(Container const&) -> span<typename Container::value_type const>;
 
 template <typename T, std::size_t N>
-span(T(&)[N]) -> span<T, N>;
+span(T (&)[N]) -> span<T, N>;
 
 template <typename T, std::size_t N>
 span(std::array<T, N>&) -> span<T, N>;
@@ -307,8 +355,8 @@ template <typename T, std::size_t N>
 span(std::array<T, N> const&) -> span<T const, N>;
 
 template <typename T, typename Dummy>
-span(T, Dummy&&) -> span<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
+span(T, Dummy &&) -> span<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
 
 } // namespace asic
 
-#endif // ASIC_SPAN_H
\ No newline at end of file
+#endif // ASIC_SPAN_HPP