From b1639c6162879555323504561fc03cbd31c7e0ed Mon Sep 17 00:00:00 2001
From: Thomas Goyne <plorkyeran@aegisub.org>
Date: Fri, 7 Mar 2014 10:58:51 -0800
Subject: [PATCH] Make the entry lists be of the appropriate type rather than
 just AssEntry

Eliminates a bajillion dynamic casts.
---
 aegisub/build/libaegisub/libaegisub.vcxproj   |   1 +
 .../libaegisub/libaegisub.vcxproj.filters     |   3 +
 .../include/libaegisub/address_of_adaptor.h   |  52 ++++++
 aegisub/src/ass_attachment.cpp                |   8 -
 aegisub/src/ass_attachment.h                  |   4 +-
 aegisub/src/ass_dialogue.cpp                  |   2 +-
 aegisub/src/ass_dialogue.h                    |   2 +-
 aegisub/src/ass_file.cpp                      | 167 ++++++++----------
 aegisub/src/ass_file.h                        |  43 ++---
 aegisub/src/ass_info.h                        |   2 +-
 aegisub/src/ass_karaoke.cpp                   |  15 +-
 aegisub/src/ass_style.cpp                     |   4 -
 aegisub/src/ass_style.h                       |   2 +-
 aegisub/src/audio_karaoke.cpp                 |   2 +-
 aegisub/src/audio_karaoke.h                   |   3 +-
 aegisub/src/audio_timing_dialogue.cpp         |  25 ++-
 aegisub/src/auto4_lua.cpp                     |   9 +-
 aegisub/src/auto4_lua_assfile.cpp             |   3 +-
 aegisub/src/base_grid.cpp                     |  50 +++---
 aegisub/src/command/edit.cpp                  |  77 ++++----
 aegisub/src/command/grid.cpp                  |   6 +-
 aegisub/src/command/subtitle.cpp              |  27 ++-
 aegisub/src/command/time.cpp                  |  15 +-
 aegisub/src/dialog_attachments.cpp            |  12 +-
 aegisub/src/dialog_kara_timing_copy.cpp       |  31 ++--
 aegisub/src/dialog_kara_timing_copy.h         |   9 +-
 aegisub/src/dialog_selection.cpp              |  12 +-
 aegisub/src/dialog_shift_times.cpp            |   9 +-
 aegisub/src/dialog_spellchecker.cpp           |  14 +-
 aegisub/src/dialog_style_editor.cpp           |  10 +-
 aegisub/src/dialog_style_manager.cpp          |  11 +-
 aegisub/src/dialog_timing_processor.cpp       |  27 +--
 aegisub/src/export_fixstyle.cpp               |  13 +-
 aegisub/src/export_framerate.cpp              |  14 +-
 aegisub/src/font_file_lister.cpp              |  18 +-
 aegisub/src/resolution_resampler.cpp          |  47 ++---
 aegisub/src/search_replace_engine.cpp         |  27 ++-
 aegisub/src/subs_controller.cpp               |  21 +--
 aegisub/src/subs_controller.h                 |   1 -
 aegisub/src/subs_edit_box.cpp                 |   3 +-
 aegisub/src/subtitle_format.cpp               |  55 +++---
 aegisub/src/subtitle_format.h                 |   1 -
 aegisub/src/subtitle_format_ass.cpp           |  95 +++++-----
 aegisub/src/subtitle_format_ebu3264.cpp       |  13 +-
 aegisub/src/subtitle_format_encore.cpp        |   6 +-
 aegisub/src/subtitle_format_microdvd.cpp      |   9 +-
 aegisub/src/subtitle_format_srt.cpp           |  11 +-
 aegisub/src/subtitle_format_transtation.cpp   |  12 +-
 aegisub/src/subtitle_format_transtation.h     |   2 +-
 aegisub/src/subtitle_format_ttxt.cpp          |  11 +-
 aegisub/src/subtitle_format_txt.cpp           |  16 +-
 aegisub/src/subtitles_provider_libass.cpp     |  42 +++--
 aegisub/src/threaded_frame_source.cpp         |  15 +-
 aegisub/src/threaded_frame_source.h           |   4 +-
 aegisub/src/utils.h                           |  13 --
 aegisub/src/video_context.cpp                 |   6 +-
 aegisub/src/video_context.h                   |   8 +-
 aegisub/src/visual_tool_drag.cpp              |  19 +-
 58 files changed, 537 insertions(+), 602 deletions(-)
 create mode 100644 aegisub/libaegisub/include/libaegisub/address_of_adaptor.h

diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj b/aegisub/build/libaegisub/libaegisub.vcxproj
index a73a35119..a8126c3f9 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj
@@ -38,6 +38,7 @@
     <ClInclude Include="$(SrcDir)common\parser.h" />
     <ClInclude Include="$(SrcDir)config.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\access.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\address_of_adaptor.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\ass\dialogue_parser.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\ass\uuencode.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\background_runner.h" />
diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj.filters b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
index a73b5aaf7..ff2bba3f6 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj.filters
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
@@ -164,6 +164,9 @@
     <ClInclude Include="$(SrcDir)include\libaegisub\owning_intrusive_list.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\address_of_adaptor.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="$(SrcDir)windows\lagi_pre.cpp">
diff --git a/aegisub/libaegisub/include/libaegisub/address_of_adaptor.h b/aegisub/libaegisub/include/libaegisub/address_of_adaptor.h
new file mode 100644
index 000000000..1ca2c33f9
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/address_of_adaptor.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include <boost/range/adaptor/transformed.hpp>
+
+namespace agi {
+namespace address_of_detail {
+	using namespace boost::adaptors;
+
+	// Tag type to select the operator| overload
+	struct address_of_tag_type { };
+
+	template<typename Iterator>
+	struct take_address_of {
+		using result_type = typename std::iterator_traits<Iterator>::pointer;
+		using input_type = typename std::iterator_traits<Iterator>::reference;
+
+		result_type operator()(input_type v) const { return &v; }
+	};
+
+	template<typename Rng>
+	auto operator|(Rng&& r, address_of_tag_type)
+		-> boost::transformed_range<take_address_of<typename Rng::iterator>, Rng>
+	{
+		return r | transformed(take_address_of<typename Rng::iterator>());
+	}
+
+	template<typename Rng>
+	auto operator|(Rng& r, address_of_tag_type)
+		-> boost::transformed_range<take_address_of<typename Rng::iterator>, Rng>
+	{
+		return r | transformed(take_address_of<typename Rng::iterator>());
+	}
+}
+
+namespace {
+	const auto address_of = address_of_detail::address_of_tag_type{};
+}
+}
diff --git a/aegisub/src/ass_attachment.cpp b/aegisub/src/ass_attachment.cpp
index a5b06dfb7..e0c482c84 100644
--- a/aegisub/src/ass_attachment.cpp
+++ b/aegisub/src/ass_attachment.cpp
@@ -59,14 +59,6 @@ AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group)
 	entry_data = entry_data.get() + agi::ass::UUEncode(data);
 }
 
-AssEntry *AssAttachment::Clone() const {
-	return new AssAttachment(*this);
-}
-
-const std::string AssAttachment::GetEntryData() const {
-	return entry_data;
-}
-
 size_t AssAttachment::GetSize() const {
 	auto header_end = entry_data.get().find('\n');
 	return entry_data.get().size() - header_end - 1;
diff --git a/aegisub/src/ass_attachment.h b/aegisub/src/ass_attachment.h
index 142b01441..ebbad86a6 100644
--- a/aegisub/src/ass_attachment.h
+++ b/aegisub/src/ass_attachment.h
@@ -46,9 +46,9 @@ public:
 	/// @param raw If false, remove the SSA filename mangling
 	std::string GetFileName(bool raw=false) const;
 
-	const std::string GetEntryData() const override;
+	const std::string GetEntryData() const override { return entry_data;  }
 	AssEntryGroup Group() const override { return group; }
-	AssEntry *Clone() const override;
+	AssAttachment *Clone() const override { return new AssAttachment(*this); }
 
 	AssAttachment(AssAttachment const& rgt);
 	AssAttachment(std::string const& header, AssEntryGroup group);
diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp
index 6954fb974..f75298d04 100644
--- a/aegisub/src/ass_dialogue.cpp
+++ b/aegisub/src/ass_dialogue.cpp
@@ -256,7 +256,7 @@ std::string AssDialogue::GetStrippedText() const {
 	return join(blocks | agi::of_type<AssDialogueBlockPlain>() | transformed(get_text_p), "");
 }
 
-AssEntry *AssDialogue::Clone() const {
+AssDialogue *AssDialogue::Clone() const {
 	auto clone = new AssDialogue(*this);
 	clone->Id = Id;
 	return clone;
diff --git a/aegisub/src/ass_dialogue.h b/aegisub/src/ass_dialogue.h
index 56af7cacc..780553ba8 100644
--- a/aegisub/src/ass_dialogue.h
+++ b/aegisub/src/ass_dialogue.h
@@ -177,7 +177,7 @@ public:
 	/// Does this line collide with the passed line?
 	bool CollidesWith(const AssDialogue *target) const;
 
-	AssEntry *Clone() const override;
+	AssDialogue *Clone() const override;
 
 	AssDialogue();
 	AssDialogue(AssDialogue const&);
diff --git a/aegisub/src/ass_file.cpp b/aegisub/src/ass_file.cpp
index 90671e469..0e76725b1 100644
--- a/aegisub/src/ass_file.cpp
+++ b/aegisub/src/ass_file.cpp
@@ -1,36 +1,19 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
-/// @file ass_file.cpp
-/// @brief Overall storage of subtitle files, undo management and more
-/// @ingroup subs_storage
-
 #include "config.h"
 
 #include "ass_file.h"
@@ -42,20 +25,20 @@
 #include "options.h"
 #include "utils.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <algorithm>
 #include <boost/algorithm/string/case_conv.hpp>
 #include <boost/filesystem/path.hpp>
 
+AssFile::AssFile() { }
+
 AssFile::~AssFile() {
-	Info.clear_and_dispose([](AssEntry *e) { delete e; });
-	Styles.clear_and_dispose([](AssEntry *e) { delete e; });
-	Events.clear_and_dispose([](AssEntry *e) { delete e; });
-	Attachments.clear_and_dispose([](AssEntry *e) { delete e; });
+	Info.clear_and_dispose([](AssInfo *e) { delete e; });
+	Styles.clear_and_dispose([](AssStyle *e) { delete e; });
+	Events.clear_and_dispose([](AssDialogue *e) { delete e; });
+	Attachments.clear_and_dispose([](AssAttachment *e) { delete e; });
 }
 
-void AssFile::LoadDefault(bool defline) {
+void AssFile::LoadDefault(bool include_dialogue_line) {
 	Info.push_back(*new AssInfo("Title", "Default Aegisub file"));
 	Info.push_back(*new AssInfo("ScriptType", "v4.00+"));
 	Info.push_back(*new AssInfo("WrapStyle", "0"));
@@ -68,15 +51,15 @@ void AssFile::LoadDefault(bool defline) {
 
 	Styles.push_back(*new AssStyle);
 
-	if (defline)
+	if (include_dialogue_line)
 		Events.push_back(*new AssDialogue);
 }
 
 AssFile::AssFile(const AssFile &from) {
-	Info.clone_from(from.Info, std::mem_fun_ref(&AssEntry::Clone), [](AssEntry *e) { delete e; });
-	Styles.clone_from(from.Styles, std::mem_fun_ref(&AssEntry::Clone), [](AssEntry *e) { delete e; });
-	Events.clone_from(from.Events, std::mem_fun_ref(&AssEntry::Clone), [](AssEntry *e) { delete e; });
-	Attachments.clone_from(from.Attachments, std::mem_fun_ref(&AssEntry::Clone), [](AssEntry *e) { delete e; });
+	Info.clone_from(from.Info, std::mem_fun_ref(&AssInfo::Clone), [](AssInfo *e) { delete e; });
+	Styles.clone_from(from.Styles, std::mem_fun_ref(&AssStyle::Clone), [](AssStyle *e) { delete e; });
+	Events.clone_from(from.Events, std::mem_fun_ref(&AssDialogue::Clone), [](AssDialogue *e) { delete e; });
+	Attachments.clone_from(from.Attachments, std::mem_fun_ref(&AssAttachment::Clone), [](AssAttachment *e) { delete e; });
 }
 
 void AssFile::swap(AssFile& from) throw() {
@@ -102,9 +85,9 @@ void AssFile::InsertAttachment(agi::fs::path const& filename) {
 }
 
 std::string AssFile::GetScriptInfo(std::string const& key) const {
-	for (const auto info : Info | agi::of_type<AssInfo>()) {
-		if (boost::iequals(key, info->Key()))
-			return info->Value();
+	for (auto const& info : Info) {
+		if (boost::iequals(key, info.Key()))
+			return info.Value();
 	}
 
 	return "";
@@ -131,12 +114,12 @@ void AssFile::SaveUIState(std::string const& key, std::string const& value) {
 }
 
 void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
-	for (auto info : Info | agi::of_type<AssInfo>()) {
-		if (boost::iequals(key, info->Key())) {
+	for (auto& info : Info) {
+		if (boost::iequals(key, info.Key())) {
 			if (value.empty())
-				delete info;
+				delete &info;
 			else
-				info->SetValue(value);
+				info.SetValue(value);
 			return;
 		}
 	}
@@ -145,47 +128,42 @@ void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
 		Info.push_back(*new AssInfo(key, value));
 }
 
-void AssFile::GetResolution(int &sw,int &sh) const {
+void AssFile::GetResolution(int &sw, int &sh) const {
 	sw = GetScriptInfoAsInt("PlayResX");
 	sh = GetScriptInfoAsInt("PlayResY");
 
-	// Gabest logic?
+	// Gabest logic: default is 384x288, assume 1280x1024 if either height or
+	// width are that, otherwise assume 4:3 if only heigh or width are set.
+	// Why 1280x1024? Who the fuck knows. Clearly just Gabest trolling everyone.
 	if (sw == 0 && sh == 0) {
 		sw = 384;
 		sh = 288;
-	} else if (sw == 0) {
-		if (sh == 1024)
-			sw = 1280;
-		else
-			sw = sh * 4 / 3;
-	} else if (sh == 0) {
-		// you are not crazy; this doesn't make any sense
-		if (sw == 1280)
-			sh = 1024;
-		else
-			sh = sw * 3 / 4;
 	}
+	else if (sw == 0)
+		sw = sh == 1024 ? 1280 : sh * 4 / 3;
+	else if (sh == 0)
+		sh = sw == 1280 ? 1024 : sw * 3 / 4;
 }
 
 std::vector<std::string> AssFile::GetStyles() const {
 	std::vector<std::string> styles;
-	for (auto style : Styles | agi::of_type<AssStyle>())
-		styles.push_back(style->name);
+	for (auto& style : Styles)
+		styles.push_back(style.name);
 	return styles;
 }
 
 AssStyle *AssFile::GetStyle(std::string const& name) {
-	for (auto style : Styles | agi::of_type<AssStyle>()) {
-		if (boost::iequals(style->name, name))
-			return style;
+	for (auto& style : Styles) {
+		if (boost::iequals(style.name, name))
+			return &style;
 	}
 	return nullptr;
 }
 
-int AssFile::Commit(wxString const& desc, int type, int amend_id, AssEntry *single_line) {
+int AssFile::Commit(wxString const& desc, int type, int amend_id, AssDialogue *single_line) {
 	PushState({desc, &amend_id, single_line});
 
-	std::set<const AssEntry*> changed_lines;
+	std::set<const AssDialogue*> changed_lines;
 	if (single_line)
 		changed_lines.insert(single_line);
 
@@ -194,50 +172,45 @@ int AssFile::Commit(wxString const& desc, int type, int amend_id, AssEntry *sing
 	return amend_id;
 }
 
-bool AssFile::CompStart(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->Start < rgt->Start;
+bool AssFile::CompStart(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.Start < rgt.Start;
 }
-bool AssFile::CompEnd(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->End < rgt->End;
+bool AssFile::CompEnd(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.End < rgt.End;
 }
-bool AssFile::CompStyle(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->Style < rgt->Style;
+bool AssFile::CompStyle(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.Style < rgt.Style;
 }
-bool AssFile::CompActor(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->Actor < rgt->Actor;
+bool AssFile::CompActor(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.Actor < rgt.Actor;
 }
-bool AssFile::CompEffect(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->Effect < rgt->Effect;
+bool AssFile::CompEffect(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.Effect < rgt.Effect;
 }
-bool AssFile::CompLayer(const AssDialogue* lft, const AssDialogue* rgt) {
-	return lft->Layer < rgt->Layer;
+bool AssFile::CompLayer(AssDialogue const& lft, AssDialogue const& rgt) {
+	return lft.Layer < rgt.Layer;
 }
 
 void AssFile::Sort(CompFunc comp, std::set<AssDialogue*> const& limit) {
 	Sort(Events, comp, limit);
 }
-namespace {
-	inline bool is_dialogue(AssEntry *e, std::set<AssDialogue*> const& limit) {
-		AssDialogue *d = dynamic_cast<AssDialogue*>(e);
-		return d && (limit.empty() || limit.count(d));
-	}
-}
 
-void AssFile::Sort(EntryList &lst, CompFunc comp, std::set<AssDialogue*> const& limit) {
-	auto compE = [&](AssEntry const& a, AssEntry const& b) {
-		return comp(static_cast<const AssDialogue*>(&a), static_cast<const AssDialogue*>(&b));
-	};
+void AssFile::Sort(EntryList<AssDialogue> &lst, CompFunc comp, std::set<AssDialogue*> const& limit) {
+	if (limit.empty()) {
+		lst.sort(comp);
+		return;
+	}
 
-	// Sort each block of AssDialogues separately, leaving everything else untouched
-	for (entryIter begin = lst.begin(); begin != lst.end(); ++begin) {
-		if (!is_dialogue(&*begin, limit)) continue;
-		entryIter end = begin;
-		while (end != lst.end() && is_dialogue(&*end, limit)) ++end;
+	// Sort each selected block separately, leaving everything else untouched
+	for (auto begin = lst.begin(); begin != lst.end(); ++begin) {
+		if (!limit.count(&*begin)) continue;
+		auto end = begin;
+		while (end != lst.end() && limit.count(&*end)) ++end;
 
-		// used instead of std::list::sort for partial list sorting
-		EntryList tmp;
+		// sort doesn't support only sorting a sublist, so move them to a temp list
+		EntryList<AssDialogue> tmp;
 		tmp.splice(tmp.begin(), lst, begin, end);
-		tmp.sort(compE);
+		tmp.sort(comp);
 		lst.splice(end, tmp);
 
 		begin = --end;
diff --git a/aegisub/src/ass_file.h b/aegisub/src/ass_file.h
index dd36272e5..207d4a271 100644
--- a/aegisub/src/ass_file.h
+++ b/aegisub/src/ass_file.h
@@ -41,33 +41,36 @@
 #include <set>
 #include <vector>
 
+class AssAttachment;
 class AssDialogue;
+class AssInfo;
 class AssStyle;
-class AssAttachment;
 class wxString;
 
-typedef boost::intrusive::make_list<AssEntry, boost::intrusive::constant_time_size<false>>::type EntryList;
-typedef EntryList::iterator entryIter;
-typedef EntryList::const_iterator constEntryIter;
+template<typename T>
+using EntryList = typename boost::intrusive::make_list<T, boost::intrusive::constant_time_size<false>, boost::intrusive::base_hook<AssEntry>>::type;
+
+template<typename T>
+using EntryIter = typename EntryList<T>::iterator;
 
 struct AssFileCommit {
 	wxString const& message;
 	int *commit_id;
-	AssEntry *single_line;
+	AssDialogue *single_line;
 };
 
 class AssFile {
 	/// A set of changes has been committed to the file (AssFile::COMMITType)
-	agi::signal::Signal<int, std::set<const AssEntry*> const&> AnnounceCommit;
+	agi::signal::Signal<int, std::set<const AssDialogue*> const&> AnnounceCommit;
 	agi::signal::Signal<AssFileCommit> PushState;
 public:
 	/// The lines in the file
-	EntryList Info;
-	EntryList Styles;
-	EntryList Events;
-	EntryList Attachments;
+	EntryList<AssInfo> Info;
+	EntryList<AssStyle> Styles;
+	EntryList<AssDialogue> Events;
+	EntryList<AssAttachment> Attachments;
 
-	AssFile() { }
+	AssFile();
 	AssFile(const AssFile &from);
 	AssFile& operator=(AssFile from);
 	~AssFile();
@@ -139,23 +142,23 @@ public:
 	/// @param commitId    Commit to amend rather than pushing a new commit
 	/// @param single_line Line which was changed, if only one line was
 	/// @return Unique identifier for the new undo group
-	int Commit(wxString const& desc, int type, int commitId = -1, AssEntry *single_line = nullptr);
+	int Commit(wxString const& desc, int type, int commitId = -1, AssDialogue *single_line = nullptr);
 
 	/// Comparison function for use when sorting
-	typedef bool (*CompFunc)(const AssDialogue* lft, const AssDialogue* rgt);
+	typedef bool (*CompFunc)(AssDialogue const& lft, AssDialogue const& rgt);
 
 	/// Compare based on start time
-	static bool CompStart(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompStart(AssDialogue const& lft, AssDialogue const& rgt);
 	/// Compare based on end time
-	static bool CompEnd(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompEnd(AssDialogue const& lft, AssDialogue const& rgt);
 	/// Compare based on style name
-	static bool CompStyle(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompStyle(AssDialogue const& lft, AssDialogue const& rgt);
 	/// Compare based on actor name
-	static bool CompActor(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompActor(AssDialogue const& lft, AssDialogue const& rgt);
 	/// Compare based on effect
-	static bool CompEffect(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompEffect(AssDialogue const& lft, AssDialogue const& rgt);
 	/// Compare based on layer
-	static bool CompLayer(const AssDialogue* lft, const AssDialogue* rgt);
+	static bool CompLayer(AssDialogue const& lft, AssDialogue const& rgt);
 
 	/// @brief Sort the dialogue lines in this file
 	/// @param comp Comparison function to use. Defaults to sorting by start time.
@@ -164,5 +167,5 @@ public:
 	/// @brief Sort the dialogue lines in the given list
 	/// @param comp Comparison function to use. Defaults to sorting by start time.
 	/// @param limit If non-empty, only lines in this set are sorted
-	static void Sort(EntryList& lst, CompFunc comp = CompStart, std::set<AssDialogue*> const& limit = std::set<AssDialogue*>());
+	static void Sort(EntryList<AssDialogue>& lst, CompFunc comp = CompStart, std::set<AssDialogue*> const& limit = std::set<AssDialogue*>());
 };
diff --git a/aegisub/src/ass_info.h b/aegisub/src/ass_info.h
index acededc66..07d3998df 100644
--- a/aegisub/src/ass_info.h
+++ b/aegisub/src/ass_info.h
@@ -26,7 +26,7 @@ public:
 	AssInfo(AssInfo const& o) = default;
 	AssInfo(std::string key, std::string value) : key(std::move(key)), value(std::move(value)) { }
 
-	AssEntry *Clone() const override { return new AssInfo(*this); }
+	AssInfo *Clone() const override { return new AssInfo(*this); }
 	AssEntryGroup Group() const override { return AssEntryGroup::INFO; }
 	const std::string GetEntryData() const override { return key + ": " + value; }
 	std::string GetSSAText() const override { return boost::iequals(key, "scripttype: v4.00+") ? "ScriptType: v4.00" : GetEntryData(); }
diff --git a/aegisub/src/ass_karaoke.cpp b/aegisub/src/ass_karaoke.cpp
index 9181d78e0..f9303395c 100644
--- a/aegisub/src/ass_karaoke.cpp
+++ b/aegisub/src/ass_karaoke.cpp
@@ -280,19 +280,18 @@ void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c
 	SubtitleSelection sel = c->selectionController->GetSelectedSet();
 
 	bool did_split = false;
-	for (entryIter it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
-		AssDialogue *diag = dynamic_cast<AssDialogue*>(&*it);
-		if (!diag || !lines.count(diag)) continue;
+	for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
+		if (!lines.count(&*it)) continue;
 
-		kara.SetLine(diag);
+		kara.SetLine(&*it);
 
 		// If there aren't at least two tags there's nothing to split
 		if (kara.size() < 2) continue;
 
-		bool in_sel = sel.count(diag) > 0;
+		bool in_sel = sel.count(&*it) > 0;
 
 		for (auto const& syl : kara) {
-			auto new_line = new AssDialogue(*diag);
+			auto new_line = new AssDialogue(*it);
 
 			new_line->Start = syl.start_time;
 			new_line->End = syl.start_time + syl.duration;
@@ -305,8 +304,8 @@ void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c
 		}
 
 		--it; // Move `it` to the last of the new lines
-		sel.erase(diag);
-		delete diag;
+		sel.erase(&*it);
+		delete &*it;
 
 		did_split = true;
 	}
diff --git a/aegisub/src/ass_style.cpp b/aegisub/src/ass_style.cpp
index 88f551929..0b9216a9d 100644
--- a/aegisub/src/ass_style.cpp
+++ b/aegisub/src/ass_style.cpp
@@ -204,10 +204,6 @@ std::string AssStyle::GetSSAText() const {
 		% Margin[0] % Margin[1] % Margin[2] % encoding);
 }
 
-AssEntry *AssStyle::Clone() const {
-	return new AssStyle(*this);
-}
-
 void AssStyle::GetEncodings(wxArrayString &encodingStrings) {
 	encodingStrings.Clear();
 	encodingStrings.Add(wxString("0 - ") + _("ANSI"));
diff --git a/aegisub/src/ass_style.h b/aegisub/src/ass_style.h
index 149fd3d13..aaf2b15a5 100644
--- a/aegisub/src/ass_style.h
+++ b/aegisub/src/ass_style.h
@@ -80,7 +80,7 @@ public:
 	const std::string GetEntryData() const override { return data; }
 	std::string GetSSAText() const override;
 	AssEntryGroup Group() const override { return AssEntryGroup::STYLE; }
-	AssEntry *Clone() const override;
+	AssStyle *Clone() const override { return new AssStyle(*this); }
 
 	/// Convert an ASS alignment to the equivalent SSA alignment
 	static int AssToSsa(int ass_align);
diff --git a/aegisub/src/audio_karaoke.cpp b/aegisub/src/audio_karaoke.cpp
index f2e9270b3..500d9bb46 100644
--- a/aegisub/src/audio_karaoke.cpp
+++ b/aegisub/src/audio_karaoke.cpp
@@ -117,7 +117,7 @@ void AudioKaraoke::OnActiveLineChanged(AssDialogue *new_line) {
 	}
 }
 
-void AudioKaraoke::OnFileChanged(int type, std::set<const AssEntry *> const& changed) {
+void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const& changed) {
 	if (enabled && (type & AssFile::COMMIT_DIAG_FULL) && (changed.empty() || changed.count(active_line))) {
 		LoadFromLine();
 		split_area->Refresh(false);
diff --git a/aegisub/src/audio_karaoke.h b/aegisub/src/audio_karaoke.h
index cf4a293a6..e026cb282 100644
--- a/aegisub/src/audio_karaoke.h
+++ b/aegisub/src/audio_karaoke.h
@@ -31,7 +31,6 @@
 #include <wx/window.h>
 
 class AssDialogue;
-class AssEntry;
 class AssKaraoke;
 class wxButton;
 
@@ -144,7 +143,7 @@ class AudioKaraoke : public wxWindow {
 	void OnActiveLineChanged(AssDialogue *new_line);
 	void OnContextMenu(wxContextMenuEvent&);
 	void OnEnableButton(wxCommandEvent &evt);
-	void OnFileChanged(int type, std::set<const AssEntry *> const& changed);
+	void OnFileChanged(int type, std::set<const AssDialogue *> const& changed);
 	void OnMouse(wxMouseEvent &event);
 	void OnPaint(wxPaintEvent &event);
 	void OnSize(wxSizeEvent &event);
diff --git a/aegisub/src/audio_timing_dialogue.cpp b/aegisub/src/audio_timing_dialogue.cpp
index f29ea64d1..748d1ac3b 100644
--- a/aegisub/src/audio_timing_dialogue.cpp
+++ b/aegisub/src/audio_timing_dialogue.cpp
@@ -715,21 +715,19 @@ void AudioTimingControllerDialogue::SetMarkers(std::vector<AudioMarker*> const&
 	AnnounceMarkerMoved();
 }
 
-static bool noncomment_dialogue(AssEntry const& e)
+static bool noncomment_dialogue(AssDialogue const& diag)
 {
-	if (const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&e))
-		return !diag->Comment;
-	return false;
+	return !diag.Comment;
 }
 
-static bool dialogue(AssEntry const& e)
+static bool dialogue(AssDialogue const&)
 {
-	return !!dynamic_cast<const AssDialogue*>(&e);
+	return true;
 }
 
 void AudioTimingControllerDialogue::RegenerateInactiveLines()
 {
-	bool (*predicate)(AssEntry const&) = inactive_line_comments->GetBool() ? dialogue : noncomment_dialogue;
+	auto predicate = inactive_line_comments->GetBool() ? dialogue : noncomment_dialogue;
 
 	bool was_empty = inactive_lines.empty();
 	inactive_lines.clear();
@@ -742,21 +740,20 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
 	case 2: // Previous and next lines
 		if (AssDialogue *line = context->selectionController->GetActiveLine())
 		{
-			entryIter current_line = context->ass->Events.iterator_to(*line);
+			auto current_line = context->ass->Events.iterator_to(*line);
 			if (current_line == context->ass->Events.end())
 				break;
 
-			entryIter prev = current_line;
+			auto prev = current_line;
 			while (--prev != context->ass->Events.begin() && !predicate(*prev)) ;
 			if (prev != context->ass->Events.begin())
-				AddInactiveLine(sel, static_cast<AssDialogue*>(&*prev));
+				AddInactiveLine(sel, &*prev);
 
 			if (mode == 2)
 			{
-				entryIter next =
-					find_if(++current_line, context->ass->Events.end(), predicate);
+				auto next = find_if(++current_line, context->ass->Events.end(), predicate);
 				if (next != context->ass->Events.end())
-					AddInactiveLine(sel, static_cast<AssDialogue*>(&*next));
+					AddInactiveLine(sel, &*next);
 			}
 		}
 		break;
@@ -766,7 +763,7 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
 		for (auto& line : context->ass->Events)
 		{
 			if (&line != active_line && predicate(line))
-				AddInactiveLine(sel, static_cast<AssDialogue*>(&line));
+				AddInactiveLine(sel, &line);
 		}
 		break;
 	}
diff --git a/aegisub/src/auto4_lua.cpp b/aegisub/src/auto4_lua.cpp
index 6ba8f4123..51b3a6c0a 100644
--- a/aegisub/src/auto4_lua.cpp
+++ b/aegisub/src/auto4_lua.cpp
@@ -277,7 +277,7 @@ namespace {
 
 		lua_pushvalue(L, 1);
 		std::unique_ptr<AssEntry> et(Automation4::LuaAssFile::LuaToAssEntry(L));
-		AssStyle *st = dynamic_cast<AssStyle*>(et.get());
+		auto st = dynamic_cast<AssStyle*>(et.get());
 		lua_pop(L, 1);
 		if (!st)
 			return luaL_error(L, "Not a style entry");
@@ -843,9 +843,8 @@ namespace Automation4 {
 		int idx = 1;
 		for (auto& line : c->ass->Events) {
 			++row;
-			auto diag = static_cast<AssDialogue*>(&line);
-			if (diag == active_line) active_idx = row;
-			if (sel.count(diag)) {
+			if (&line == active_line) active_idx = row;
+			if (sel.count(&line)) {
 				push_value(L, row);
 				lua_rawseti(L, -2, idx++);
 			}
@@ -928,7 +927,7 @@ namespace Automation4 {
 							throw LuaForEachBreak();
 						}
 
-						AssDialogue *diag = dynamic_cast<AssDialogue*>(lines[cur - 1]);
+						auto diag = dynamic_cast<AssDialogue*>(lines[cur - 1]);
 						if (!diag) {
 							wxLogError("Selected row %d is not a dialogue line", cur);
 							throw LuaForEachBreak();
diff --git a/aegisub/src/auto4_lua_assfile.cpp b/aegisub/src/auto4_lua_assfile.cpp
index 87350f3e3..ecb77e661 100644
--- a/aegisub/src/auto4_lua_assfile.cpp
+++ b/aegisub/src/auto4_lua_assfile.cpp
@@ -37,6 +37,7 @@
 #include "auto4_lua.h"
 
 #include "auto4_lua_utils.h"
+#include "ass_attachment.h"
 #include "ass_dialogue.h"
 #include "ass_info.h"
 #include "ass_file.h"
@@ -535,7 +536,7 @@ namespace Automation4 {
 	int LuaAssFile::LuaParseKaraokeData(lua_State *L)
 	{
 		auto e = LuaToAssEntry(L);
-		AssDialogue *dia = dynamic_cast<AssDialogue*>(e.get());
+		auto dia = dynamic_cast<AssDialogue*>(e.get());
 		luaL_argcheck(L, dia, 1, "Subtitle line must be a dialogue line");
 
 		int idx = 0;
diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp
index 0e00ff47a..9ac3f3edf 100644
--- a/aegisub/src/base_grid.cpp
+++ b/aegisub/src/base_grid.cpp
@@ -52,8 +52,6 @@
 #include "video_context.h"
 #include "video_slider.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <algorithm>
 #include <boost/range/algorithm.hpp>
 #include <cmath>
@@ -82,13 +80,6 @@ enum RowColor {
 	COLOR_LEFT_COL
 };
 
-template<class S1, class S2, class D>
-static inline void set_difference(const S1 &src1, const S2 &src2, D &dst) {
-	std::set_difference(
-		src1.begin(), src1.end(), src2.begin(), src2.end(),
-		std::inserter(dst, dst.begin()));
-}
-
 namespace std {
 	template <typename T>
 	struct hash<boost::flyweight<T>> {
@@ -265,9 +256,9 @@ void BaseGrid::UpdateMaps() {
 	index_line_map.clear();
 	line_index_map.clear();
 
-	for (auto curdiag : context->ass->Events | agi::of_type<AssDialogue>()) {
-		line_index_map[curdiag] = (int)index_line_map.size();
-		index_line_map.push_back(curdiag);
+	for (auto& curdiag : context->ass->Events) {
+		line_index_map[&curdiag] = (int)index_line_map.size();
+		index_line_map.push_back(&curdiag);
 	}
 
 	auto sorted = index_line_map;
@@ -751,6 +742,7 @@ void BaseGrid::SetColumnWidths() {
 
 	std::unordered_map<boost::flyweight<std::string>, int> widths;
 	auto get_width = [&](boost::flyweight<std::string> const& str) -> int {
+		if (str.get().empty()) return 0;
 		auto it = widths.find(str);
 		if (it != end(widths)) return it->second;
 		int width = dc.GetTextExtent(to_wx(str)).GetWidth();
@@ -766,24 +758,22 @@ void BaseGrid::SetColumnWidths() {
 	int maxLayer = 0;
 	int maxStart = 0;
 	int maxEnd = 0;
-	for (int i = 0; i < GetRows(); i++) {
-		AssDialogue *curDiag = GetDialogue(i);
-
-		maxLayer = std::max(maxLayer, curDiag->Layer);
-		actorLen = std::max(actorLen, get_width(curDiag->Actor));
-		styleLen = std::max(styleLen, get_width(curDiag->Style));
-		effectLen = std::max(effectLen, get_width(curDiag->Effect));
+	for (auto const& diag : context->ass->Events) {
+		maxLayer = std::max(maxLayer, diag.Layer);
+		actorLen = std::max(actorLen, get_width(diag.Actor));
+		styleLen = std::max(styleLen, get_width(diag.Style));
+		effectLen = std::max(effectLen, get_width(diag.Effect));
 
 		// Margins
 		for (int j = 0; j < 3; j++) {
-			if (curDiag->Margin[j])
+			if (diag.Margin[j])
 				showMargin[j] = true;
 		}
 
 		// Times
 		if (byFrame) {
-			maxStart = std::max(maxStart, context->videoController->FrameAtTime(curDiag->Start, agi::vfr::START));
-			maxEnd = std::max(maxEnd, context->videoController->FrameAtTime(curDiag->End, agi::vfr::END));
+			maxStart = std::max(maxStart, context->videoController->FrameAtTime(diag.Start, agi::vfr::START));
+			maxEnd = std::max(maxEnd, context->videoController->FrameAtTime(diag.End, agi::vfr::END));
 		}
 	}
 
@@ -991,15 +981,19 @@ void BaseGrid::SetSelectionAndActive(Selection const& new_selection, AssDialogue
 }
 
 void BaseGrid::PrevLine() {
-	int cur_line_i = GetDialogueIndex(GetActiveLine());
-	if (AssDialogue *prev_line = GetDialogue(cur_line_i-1))
-		SetSelectionAndActive({ prev_line }, prev_line);
+	if (!active_line) return;
+	auto it = context->ass->Events.iterator_to(*active_line);
+	if (it != context->ass->Events.begin()) {
+		--it;
+		SetSelectionAndActive({&*it}, &*it);
+	}
 }
 
 void BaseGrid::NextLine() {
-	int cur_line_i = GetDialogueIndex(GetActiveLine());
-	if (AssDialogue *next_line = GetDialogue(cur_line_i+1))
-		SetSelectionAndActive({ next_line }, next_line);
+	if (!active_line) return;
+	auto it = context->ass->Events.iterator_to(*active_line);
+	if (++it != context->ass->Events.end())
+		SetSelectionAndActive({&*it}, &*it);
 }
 
 void BaseGrid::AnnounceActiveLineChanged(AssDialogue *new_line) {
diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp
index a85e4e96c..ffb6663f5 100644
--- a/aegisub/src/command/edit.cpp
+++ b/aegisub/src/command/edit.cpp
@@ -53,6 +53,7 @@
 #include "../utils.h"
 #include "../video_context.h"
 
+#include <libaegisub/address_of_adaptor.h>
 #include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
@@ -485,12 +486,11 @@ struct edit_find_replace : public Command {
 	}
 };
 
-static std::string get_entry_data(AssDialogue *d) { return d->GetEntryData(); }
+static std::string get_entry_data(AssDialogue &d) { return d.GetEntryData(); }
 static void copy_lines(agi::Context *c) {
 	SubtitleSelection sel = c->selectionController->GetSelectedSet();
 	SetClipboard(join(c->ass->Events
-		| agi::of_type<AssDialogue>()
-		| filtered([&](AssDialogue *d) { return sel.count(d); })
+		| filtered([&](AssDialogue &d) { return sel.count(&d); })
 		| transformed(get_entry_data),
 		"\r\n"));
 }
@@ -503,25 +503,25 @@ static void delete_lines(agi::Context *c, wxString const& commit_message) {
 	AssDialogue *post_sel = nullptr;
 	bool hit_selection = false;
 
-	for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-		if (sel.count(diag))
+	for (auto& diag : c->ass->Events) {
+		if (sel.count(&diag))
 			hit_selection = true;
 		else if (hit_selection && !post_sel) {
-			post_sel = diag;
+			post_sel = &diag;
 			break;
 		}
 		else
-			pre_sel = diag;
+			pre_sel = &diag;
 	}
 
 	// Remove the selected lines, but defer the deletion until after we select
 	// different lines. We can't just change the selection first because we may
 	// need to create a new dialogue line for it, and we can't select dialogue
 	// lines until after they're committed.
-	std::vector<std::unique_ptr<AssEntry>> to_delete;
-	c->ass->Events.remove_and_dispose_if([&sel](AssEntry const& e) {
-		return sel.count(const_cast<AssDialogue *>(static_cast<const AssDialogue*>(&e)));
-	}, [&](AssEntry *e) {
+	std::vector<std::unique_ptr<AssDialogue>> to_delete;
+	c->ass->Events.remove_and_dispose_if([&sel](AssDialogue const& e) {
+		return sel.count(const_cast<AssDialogue *>(&e));
+	}, [&](AssDialogue *e) {
 		to_delete.emplace_back(e);
 	});
 
@@ -591,35 +591,28 @@ struct edit_line_delete : public validate_sel_nonempty {
 	}
 };
 
-struct in_selection : public std::unary_function<AssEntry, bool> {
-	SubtitleSelectionController::Selection const& sel;
-	in_selection(SubtitleSelectionController::Selection const& sel) : sel(sel) { }
-	bool operator()(AssEntry const& e) const {
-		const AssDialogue *d = dynamic_cast<const AssDialogue*>(&e);
-		return d && sel.count(const_cast<AssDialogue *>(d));
-	}
-};
-
 static void duplicate_lines(agi::Context *c, int shift) {
-	in_selection sel(c->selectionController->GetSelectedSet());
+	auto const& sel = c->selectionController->GetSelectedSet();
+	auto in_selection = [&](AssDialogue const& d) { return sel.count(const_cast<AssDialogue *>(&d)); };
+
 	SubtitleSelectionController::Selection new_sel;
 	AssDialogue *new_active = nullptr;
 
-	entryIter start = c->ass->Events.begin();
-	entryIter end = c->ass->Events.end();
+	auto start = c->ass->Events.begin();
+	auto end = c->ass->Events.end();
 	while (start != end) {
 		// Find the first line in the selection
-		start = find_if(start, end, sel);
+		start = find_if(start, end, in_selection);
 		if (start == end) break;
 
 		// And the last line in this contiguous selection
-		entryIter insert_pos = find_if_not(start, end, sel);
-		entryIter last = std::prev(insert_pos);
+		auto insert_pos = find_if_not(start, end, in_selection);
+		auto last = std::prev(insert_pos);
 
 		// Duplicate each of the selected lines, inserting them in a block
 		// after the selected block
 		do {
-			auto old_diag = static_cast<AssDialogue*>(&*start);
+			auto old_diag = &*start;
 			auto  new_diag = new AssDialogue(*old_diag);
 
 			c->ass->Events.insert(insert_pos, *new_diag);
@@ -703,10 +696,9 @@ static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDi
 	SubtitleSelection sel = c->selectionController->GetSelectedSet();
 
 	AssDialogue *first = nullptr;
-	for (entryIter it = c->ass->Events.begin(); it != c->ass->Events.end(); ) {
-		AssDialogue *diag = dynamic_cast<AssDialogue*>(&*it++);
-		if (!diag || !sel.count(diag))
-			continue;
+	for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ) {
+		AssDialogue *diag = &*it++;
+		if (!sel.count(diag)) continue;
 		if (!first) {
 			first = diag;
 			continue;
@@ -772,7 +764,7 @@ static bool try_paste_lines(agi::Context *c) {
 	boost::trim_left(data);
 	if (!boost::starts_with(data, "Dialogue:")) return false;
 
-	EntryList parsed;
+	EntryList<AssDialogue> parsed;
 	boost::char_separator<char> sep("\r\n");
 	for (auto curdata : boost::tokenizer<boost::char_separator<char>>(data, sep)) {
 		boost::trim(curdata);
@@ -780,15 +772,15 @@ static bool try_paste_lines(agi::Context *c) {
 			parsed.push_back(*new AssDialogue(curdata));
 		}
 		catch (...) {
-			parsed.clear_and_dispose([](AssEntry *e) { delete e; });
+			parsed.clear_and_dispose([](AssDialogue *e) { delete e; });
 			return false;
 		}
 	}
 
-	AssDialogue *new_active = static_cast<AssDialogue *>(&*parsed.begin());
+	AssDialogue *new_active = &*parsed.begin();
 	SubtitleSelection new_selection;
 	for (auto& line : parsed)
-		new_selection.insert(static_cast<AssDialogue *>(&line));
+		new_selection.insert(&line);
 
 	auto pos = c->ass->Events.iterator_to(*c->selectionController->GetActiveLine());
 	c->ass->Events.splice(pos, parsed, parsed.begin(), parsed.end());
@@ -858,9 +850,9 @@ struct edit_line_paste_over : public Command {
 				std::unique_ptr<AssDialogue> deleter(new_line);
 				if (pos == c->ass->Events.end()) return nullptr;
 
-				AssDialogue *ret = paste_over(c->parent, pasteOverOptions, new_line, static_cast<AssDialogue*>(&*pos));
+				AssDialogue *ret = paste_over(c->parent, pasteOverOptions, new_line, &*pos);
 				if (ret)
-					pos = find_if(next(pos), c->ass->Events.end(), cast<AssDialogue*>());
+					++pos;
 				return ret;
 			});
 		}
@@ -871,8 +863,8 @@ struct edit_line_paste_over : public Command {
 			std::vector<AssDialogue*> sorted_selection;
 			sorted_selection.reserve(sel.size());
 			for (auto& line : c->ass->Events) {
-				if (sel.count(static_cast<AssDialogue*>(&line)))
-					sorted_selection.push_back(static_cast<AssDialogue*>(&line));
+				if (sel.count(&line))
+					sorted_selection.push_back(&line);
 			}
 
 			auto pos = begin(sorted_selection);
@@ -936,7 +928,10 @@ struct edit_line_recombine : public validate_sel_multiple {
 		auto active_line = c->selectionController->GetActiveLine();
 
 		std::vector<AssDialogue*> sel(sel_set.begin(), sel_set.end());
-		boost::sort(sel, &AssFile::CompStart);
+		boost::sort(sel, [](const AssDialogue *a, const AssDialogue *b) {
+			return a->Start < b->Start;
+		});
+
 		for (auto &diag : sel)
 			diag->Text = trim_text(diag->Text);
 
@@ -983,7 +978,7 @@ struct edit_line_recombine : public validate_sel_multiple {
 
 		// Remove now non-existent lines from the selection
 		SubtitleSelection lines, new_sel;
-		boost::copy(c->ass->Events | agi::of_type<AssDialogue>(), inserter(lines, lines.begin()));
+		boost::copy(c->ass->Events | agi::address_of, inserter(lines, lines.begin()));
 		boost::set_intersection(lines, sel_set, inserter(new_sel, new_sel.begin()));
 
 		if (new_sel.empty())
diff --git a/aegisub/src/command/grid.cpp b/aegisub/src/command/grid.cpp
index b51043c63..e63a70d22 100644
--- a/aegisub/src/command/grid.cpp
+++ b/aegisub/src/command/grid.cpp
@@ -79,7 +79,7 @@ struct grid_line_next_create : public Command {
 			newline->End = cur->End + OPT_GET("Timing/Default Duration")->GetInt();
 			newline->Style = cur->Style;
 
-			entryIter pos = c->ass->Events.iterator_to(*cur);
+			auto pos = c->ass->Events.iterator_to(*cur);
 			c->ass->Events.insert(++pos, *newline);
 			c->ass->Commit(_("line insertion"), AssFile::COMMIT_DIAG_ADDREM);
 			c->selectionController->NextLine();
@@ -328,9 +328,7 @@ static bool move_one(T begin, T end, U const& to_move, int step) {
 	size_t move_count = 0;
 	auto prev = end;
 	for (auto it = begin; it != end; std::advance(it, step)) {
-		auto cur = dynamic_cast<typename U::key_type>(&*it);
-		if (!cur) continue;
-
+		auto cur = &*it;
 		if (!to_move.count(cur))
 			prev = it;
 		else if (prev != end) {
diff --git a/aegisub/src/command/subtitle.cpp b/aegisub/src/command/subtitle.cpp
index 89ee24ef6..80bfe9544 100644
--- a/aegisub/src/command/subtitle.cpp
+++ b/aegisub/src/command/subtitle.cpp
@@ -53,10 +53,11 @@
 #include "../utils.h"
 #include "../video_context.h"
 
+#include <libaegisub/address_of_adaptor.h>
 #include <libaegisub/charset_conv.h>
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
+#include <boost/range/algorithm.hpp>
 #include <wx/msgdlg.h>
 #include <wx/choicdlg.h>
 
@@ -124,7 +125,7 @@ static void insert_subtitle_at_video(agi::Context *c, bool after) {
 	def->End = video_ms + OPT_GET("Timing/Default Duration")->GetInt();
 	def->Style = c->selectionController->GetActiveLine()->Style;
 
-	entryIter pos = c->ass->Events.iterator_to(*c->selectionController->GetActiveLine());
+	auto pos = c->ass->Events.iterator_to(*c->selectionController->GetActiveLine());
 	if (after) ++pos;
 
 	c->ass->Events.insert(pos, *def);
@@ -147,8 +148,8 @@ struct subtitle_insert_after : public validate_nonempty_selection {
 		new_line->Start = active_line->End;
 		new_line->End = new_line->Start + OPT_GET("Timing/Default Duration")->GetInt();
 
-		for (entryIter it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
-			AssDialogue *diag = static_cast<AssDialogue*>(&*it);
+		for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
+			AssDialogue *diag = &*it;
 
 			// Limit the line to the available time
 			if (diag->Start >= new_line->Start)
@@ -192,8 +193,8 @@ struct subtitle_insert_before : public validate_nonempty_selection {
 		new_line->End = active_line->Start;
 		new_line->Start = new_line->End - OPT_GET("Timing/Default Duration")->GetInt();
 
-		for (entryIter it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
-			auto diag = static_cast<AssDialogue*>(&*it);
+		for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {
+			auto diag = &*it;
 
 			// Limit the line to the available time
 			if (diag->End <= new_line->End)
@@ -372,9 +373,7 @@ struct subtitle_select_all : public Command {
 
 	void operator()(agi::Context *c) override {
 		SubtitleSelection sel;
-		transform(c->ass->Events.begin(), c->ass->Events.end(),
-			inserter(sel, sel.begin()), cast<AssDialogue*>());
-		sel.erase(nullptr);
+		boost::copy(c->ass->Events | agi::address_of, inserter(sel, sel.end()));
 		c->selectionController->SetSelectedSet(sel);
 	}
 };
@@ -394,13 +393,13 @@ struct subtitle_select_visible : public Command {
 		SubtitleSelectionController::Selection new_selection;
 		int frame = c->videoController->GetFrameN();
 
-		for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-			if (c->videoController->FrameAtTime(diag->Start, agi::vfr::START) <= frame &&
-				c->videoController->FrameAtTime(diag->End, agi::vfr::END) >= frame)
+		for (auto& diag : c->ass->Events) {
+			if (c->videoController->FrameAtTime(diag.Start, agi::vfr::START) <= frame &&
+				c->videoController->FrameAtTime(diag.End, agi::vfr::END) >= frame)
 			{
 				if (new_selection.empty())
-					c->selectionController->SetActiveLine(diag);
-				new_selection.insert(diag);
+					c->selectionController->SetActiveLine(&diag);
+				new_selection.insert(&diag);
 			}
 		}
 
diff --git a/aegisub/src/command/time.cpp b/aegisub/src/command/time.cpp
index 5bceb7155..eeb73017e 100644
--- a/aegisub/src/command/time.cpp
+++ b/aegisub/src/command/time.cpp
@@ -45,7 +45,6 @@
 #include "../selection_controller.h"
 #include "../video_context.h"
 
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
 #include <algorithm>
@@ -67,8 +66,8 @@ namespace {
 			if (sel.size() < 2) return !sel.empty();
 
 			size_t found = 0;
-			for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-				if (sel.count(diag)) {
+			for (auto& diag : c->ass->Events) {
+				if (sel.count(&diag)) {
 					if (++found == sel.size())
 						return true;
 				}
@@ -84,14 +83,14 @@ static void adjoin_lines(agi::Context *c, bool set_start) {
 	AssDialogue *prev = nullptr;
 	size_t seen = 0;
 	bool prev_sel = false;
-	for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-		bool cur_sel = !!sel.count(diag);
+	for (auto diag : c->ass->Events) {
+		bool cur_sel = !!sel.count(&diag);
 		if (prev) {
 			// One row selections act as if the previous or next line was selected
 			if (set_start && cur_sel && (sel.size() == 1 || prev_sel))
-				diag->Start = prev->End;
+				diag.Start = prev->End;
 			else if (!set_start && prev_sel && (cur_sel || sel.size() == 1))
-				prev->End = diag->Start;
+				prev->End = diag.Start;
 		}
 
 		if (seen == sel.size())
@@ -100,7 +99,7 @@ static void adjoin_lines(agi::Context *c, bool set_start) {
 		if (cur_sel)
 			++seen;
 
-		prev = diag;
+		prev = &diag;
 		prev_sel = cur_sel;
 	}
 
diff --git a/aegisub/src/dialog_attachments.cpp b/aegisub/src/dialog_attachments.cpp
index dc4de6e18..50434ed72 100644
--- a/aegisub/src/dialog_attachments.cpp
+++ b/aegisub/src/dialog_attachments.cpp
@@ -50,8 +50,6 @@
 #include "options.h"
 #include "utils.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 DialogAttachments::DialogAttachments(wxWindow *parent, AssFile *ass)
 : wxDialog(parent, -1, _("Attachment List"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
 , ass(ass)
@@ -99,12 +97,12 @@ void DialogAttachments::UpdateList() {
 	listView->InsertColumn(1, _("Size"), wxLIST_FORMAT_LEFT, 100);
 	listView->InsertColumn(2, _("Group"), wxLIST_FORMAT_LEFT, 100);
 
-	for (auto attach : ass->Attachments | agi::of_type<AssAttachment>()) {
+	for (auto& attach : ass->Attachments) {
 		int row = listView->GetItemCount();
-		listView->InsertItem(row, to_wx(attach->GetFileName(true)));
-		listView->SetItem(row, 1, PrettySize(attach->GetSize()));
-		listView->SetItem(row, 2, to_wx(attach->GroupHeader()));
-		listView->SetItemPtrData(row, wxPtrToUInt(attach));
+		listView->InsertItem(row, to_wx(attach.GetFileName(true)));
+		listView->SetItem(row, 1, PrettySize(attach.GetSize()));
+		listView->SetItem(row, 2, to_wx(attach.GroupHeader()));
+		listView->SetItemPtrData(row, wxPtrToUInt(&attach));
 	}
 }
 
diff --git a/aegisub/src/dialog_kara_timing_copy.cpp b/aegisub/src/dialog_kara_timing_copy.cpp
index 135cca6ee..030bff8e3 100644
--- a/aegisub/src/dialog_kara_timing_copy.cpp
+++ b/aegisub/src/dialog_kara_timing_copy.cpp
@@ -601,8 +601,8 @@ void DialogKanjiTimer::OnAccept(wxCommandEvent &) {
 
 	if (display->GetRemainingSource() > 0)
 		wxMessageBox(_("Group all of the source text."),_("Error"),wxICON_EXCLAMATION | wxOK);
-	else if (AssDialogue *destLine = dynamic_cast<AssDialogue*>(currentDestinationLine)) {
-		LinesToChange.push_back(std::make_pair(destLine, display->GetOutputLine()));
+	else {
+		LinesToChange.push_back(std::make_pair(currentDestinationLine, display->GetOutputLine()));
 
 		currentSourceLine = FindNextStyleMatch(currentSourceLine, from_wx(SourceStyle->GetValue()));
 		currentDestinationLine = FindNextStyleMatch(currentDestinationLine, from_wx(DestStyle->GetValue()));
@@ -644,21 +644,13 @@ void DialogKanjiTimer::OnKeyDown(wxKeyEvent &event) {
 
 void DialogKanjiTimer::ResetForNewLine()
 {
-	AssDialogue *src = nullptr;
-	AssDialogue *dst = nullptr;
-
-	if (currentSourceLine)
-		src = dynamic_cast<AssDialogue*>(currentSourceLine);
-	if (currentDestinationLine)
-		dst = dynamic_cast<AssDialogue*>(currentDestinationLine);
-
-	if (!src || !dst)
+	if (!currentSourceLine || !currentDestinationLine)
 	{
-		src = dst = nullptr;
+		currentSourceLine = currentDestinationLine = nullptr;
 		wxBell();
 	}
 
-	display->SetInputData(src, dst);
+	display->SetInputData(currentSourceLine, currentDestinationLine);
 
 	TryAutoMatch();
 
@@ -672,25 +664,24 @@ void DialogKanjiTimer::TryAutoMatch()
 }
 
 template<typename Iterator>
-static AssEntry *find_next(Iterator from, Iterator to, std::string const& style_name) {
+static AssDialogue *find_next(Iterator from, Iterator to, std::string const& style_name) {
 	for (; from != to; ++from)
 	{
-		AssDialogue *dlg = dynamic_cast<AssDialogue*>(&*from);
-		if (dlg && dlg->Style == style_name && !dlg->Text.get().empty())
-			return dlg;
+		if (from->Style == style_name && !from->Text.get().empty())
+			return &*from;
 	}
 
 	return nullptr;
 }
 
-AssEntry *DialogKanjiTimer::FindNextStyleMatch(AssEntry *search_from, const std::string &search_style)
+AssDialogue *DialogKanjiTimer::FindNextStyleMatch(AssDialogue *search_from, const std::string &search_style)
 {
 	if (!search_from) return search_from;
 	return find_next(++subs->Events.iterator_to(*search_from), subs->Events.end(), search_style);
 }
 
-AssEntry *DialogKanjiTimer::FindPrevStyleMatch(AssEntry *search_from, const std::string &search_style)
+AssDialogue *DialogKanjiTimer::FindPrevStyleMatch(AssDialogue *search_from, const std::string &search_style)
 {
 	if (!search_from) return search_from;
-	return find_next(EntryList::reverse_iterator(subs->Events.iterator_to(*search_from)), subs->Events.rend(), search_style);
+	return find_next(EntryList<AssDialogue>::reverse_iterator(subs->Events.iterator_to(*search_from)), subs->Events.rend(), search_style);
 }
diff --git a/aegisub/src/dialog_kara_timing_copy.h b/aegisub/src/dialog_kara_timing_copy.h
index c74f0ba1a..ab61b3ba8 100644
--- a/aegisub/src/dialog_kara_timing_copy.h
+++ b/aegisub/src/dialog_kara_timing_copy.h
@@ -39,7 +39,6 @@
 
 namespace agi { struct Context; }
 class AssDialogue;
-class AssEntry;
 class AssFile;
 class KaraokeLineMatchDisplay;
 class wxComboBox;
@@ -55,8 +54,8 @@ class DialogKanjiTimer : public wxDialog {
 
 	std::vector<std::pair<AssDialogue*, std::string>> LinesToChange;
 
-	AssEntry *currentSourceLine = nullptr;
-	AssEntry *currentDestinationLine = nullptr;
+	AssDialogue *currentSourceLine = nullptr;
+	AssDialogue *currentDestinationLine = nullptr;
 
 	void OnClose(wxCommandEvent &event);
 	void OnStart(wxCommandEvent &event);
@@ -71,8 +70,8 @@ class DialogKanjiTimer : public wxDialog {
 	void ResetForNewLine();
 	void TryAutoMatch();
 
-	AssEntry *FindNextStyleMatch(AssEntry *search_from, const std::string &search_style);
-	AssEntry *FindPrevStyleMatch(AssEntry *search_from, const std::string &search_style);
+	AssDialogue *FindNextStyleMatch(AssDialogue *search_from, const std::string &search_style);
+	AssDialogue *FindPrevStyleMatch(AssDialogue *search_from, const std::string &search_style);
 
 public:
 	DialogKanjiTimer(agi::Context *context);
diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp
index fdc29027e..d09384c06 100644
--- a/aegisub/src/dialog_selection.cpp
+++ b/aegisub/src/dialog_selection.cpp
@@ -34,8 +34,6 @@
 #include "selection_controller.h"
 #include "utils.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <algorithm>
 #include <boost/algorithm/string/find.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -77,12 +75,12 @@ static std::set<AssDialogue*> process(std::string const& match_text, bool match_
 	auto predicate = SearchReplaceEngine::GetMatcher(settings);
 
 	std::set<AssDialogue*> matches;
-	for (auto diag : ass->Events | agi::of_type<AssDialogue>()) {
-		if (diag->Comment && !comments) continue;
-		if (!diag->Comment && !dialogue) continue;
+	for (auto& diag : ass->Events) {
+		if (diag.Comment && !comments) continue;
+		if (!diag.Comment && !dialogue) continue;
 
-		if (invert != predicate(diag, 0))
-			matches.insert(diag);
+		if (invert != predicate(&diag, 0))
+			matches.insert(&diag);
 	}
 
 	return matches;
diff --git a/aegisub/src/dialog_shift_times.cpp b/aegisub/src/dialog_shift_times.cpp
index 42f3cea2b..772ee15ec 100644
--- a/aegisub/src/dialog_shift_times.cpp
+++ b/aegisub/src/dialog_shift_times.cpp
@@ -38,7 +38,6 @@
 #include <libaegisub/fs.h>
 #include <libaegisub/io.h>
 #include <libaegisub/log.h>
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/path.h>
 #include <libaegisub/util.h>
 
@@ -354,10 +353,10 @@ void DialogShiftTimes::Process(wxCommandEvent &) {
 	int block_start = 0;
 	json::Array shifted_blocks;
 
-	for (auto line : context->ass->Events | agi::of_type<AssDialogue>()) {
+	for (auto& line : context->ass->Events) {
 		++row_number;
 
-		if (!sel.count(line)) {
+		if (!sel.count(&line)) {
 			if (block_start) {
 				json::Object block;
 				block["start"] = block_start;
@@ -372,9 +371,9 @@ void DialogShiftTimes::Process(wxCommandEvent &) {
 			block_start = row_number;
 
 		if (start)
-			line->Start = Shift(line->Start, shift, by_time, agi::vfr::START);
+			line.Start = Shift(line.Start, shift, by_time, agi::vfr::START);
 		if (end)
-			line->End = Shift(line->End, shift, by_time, agi::vfr::END);
+			line.End = Shift(line.End, shift, by_time, agi::vfr::END);
 	}
 
 	context->ass->Commit(_("shifting"), AssFile::COMMIT_DIAG_TIME);
diff --git a/aegisub/src/dialog_spellchecker.cpp b/aegisub/src/dialog_spellchecker.cpp
index 541e5d2f9..10d4ee464 100644
--- a/aegisub/src/dialog_spellchecker.cpp
+++ b/aegisub/src/dialog_spellchecker.cpp
@@ -212,19 +212,17 @@ bool DialogSpellChecker::FindNext() {
 	if (CheckLine(active_line, start_pos, &commit_id))
 		return true;
 
-	entryIter it = context->ass->Events.iterator_to(*active_line);
+	auto it = context->ass->Events.iterator_to(*active_line);
 
 	// Note that it is deliberate that the start line is checked twice, as if
 	// the cursor is past the first misspelled word in the current line, that
 	// word should be hit last
 	while(!has_looped || active_line != start_line) {
-		do {
-			// Wrap around to the beginning if we hit the end
-			if (++it == context->ass->Events.end()) {
-				it = context->ass->Events.begin();
-				has_looped = true;
-			}
-		} while (!(active_line = dynamic_cast<AssDialogue*>(&*it)));
+		// Wrap around to the beginning if we hit the end
+		if (++it == context->ass->Events.end()) {
+			it = context->ass->Events.begin();
+			has_looped = true;
+		}
 
 		if (CheckLine(active_line, 0, &commit_id))
 			return true;
diff --git a/aegisub/src/dialog_style_editor.cpp b/aegisub/src/dialog_style_editor.cpp
index bc5bdfefd..bf730b373 100644
--- a/aegisub/src/dialog_style_editor.cpp
+++ b/aegisub/src/dialog_style_editor.cpp
@@ -86,19 +86,19 @@ class StyleRenamer {
 		found_any = false;
 		do_replace = replace;
 
-		for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-			if (diag->Style == source_name) {
+		for (auto& diag : c->ass->Events) {
+			if (diag.Style == source_name) {
 				if (replace)
-					diag->Style = new_name;
+					diag.Style = new_name;
 				else
 					found_any = true;
 			}
 
-			boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
+			boost::ptr_vector<AssDialogueBlock> blocks(diag.ParseTags());
 			for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
 				block->ProcessParameters(&StyleRenamer::ProcessTag, this);
 			if (replace)
-				diag->UpdateText(blocks);
+				diag.UpdateText(blocks);
 
 			if (found_any) return;
 		}
diff --git a/aegisub/src/dialog_style_manager.cpp b/aegisub/src/dialog_style_manager.cpp
index 8737a6a1b..1783a72f2 100644
--- a/aegisub/src/dialog_style_manager.cpp
+++ b/aegisub/src/dialog_style_manager.cpp
@@ -54,7 +54,6 @@
 
 #include <libaegisub/fs.h>
 #include <libaegisub/path.h>
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/split.h>
 #include <libaegisub/util.h>
 
@@ -288,9 +287,9 @@ void DialogStyleManager::LoadCurrentStyles(int commit_type) {
 		CurrentList->Clear();
 		styleMap.clear();
 
-		for (auto style : c->ass->Styles | agi::of_type<AssStyle>()) {
-			CurrentList->Append(to_wx(style->name));
-			styleMap.push_back(style);
+		for (auto& style : c->ass->Styles) {
+			CurrentList->Append(to_wx(style.name));
+			styleMap.push_back(&style);
 		}
 	}
 
@@ -780,10 +779,8 @@ void DialogStyleManager::MoveStyles(bool storage, int type) {
 		// Replace styles
 		size_t curn = 0;
 		for (auto it = c->ass->Styles.begin(); it != c->ass->Styles.end(); ++it) {
-			if (!dynamic_cast<AssStyle*>(&*it)) continue;
-
 			auto new_style_at_pos = c->ass->Styles.iterator_to(*styleMap[curn]);
-			EntryList::node_algorithms::swap_nodes(it.pointed_node(), new_style_at_pos.pointed_node());
+			EntryList<AssStyle>::node_algorithms::swap_nodes(it.pointed_node(), new_style_at_pos.pointed_node());
 			if (++curn == styleMap.size()) break;
 			it = new_style_at_pos;
 		}
diff --git a/aegisub/src/dialog_timing_processor.cpp b/aegisub/src/dialog_timing_processor.cpp
index 2c83d9046..be888e136 100644
--- a/aegisub/src/dialog_timing_processor.cpp
+++ b/aegisub/src/dialog_timing_processor.cpp
@@ -48,8 +48,12 @@
 #include "utils.h"
 #include "video_context.h"
 
+#include <libaegisub/address_of_adaptor.h>
+
 #include <algorithm>
+#include <boost/range/adaptor/filtered.hpp>
 #include <boost/range/algorithm.hpp>
+#include <boost/range/algorithm_ext.hpp>
 #include <functional>
 
 #include <wx/button.h>
@@ -63,6 +67,8 @@
 #include <wx/textctrl.h>
 #include <wx/valnum.h>
 
+using namespace boost::adaptors;
+
 namespace {
 using std::placeholders::_1;
 
@@ -288,10 +294,6 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) {
 	EndModal(0);
 }
 
-static bool bad_line(std::set<std::string> *styles, AssDialogue *d) {
-	return !d || d->Comment || styles->find(d->Style) == styles->end();
-}
-
 std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
 	std::set<std::string> styles;
 	for (size_t i = 0; i < StyleList->GetCount(); ++i) {
@@ -301,14 +303,13 @@ std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
 
 	std::vector<AssDialogue*> sorted;
 
-	if (onlySelection->IsChecked()) {
-		SubtitleSelection sel = c->selectionController->GetSelectedSet();
-		copy_if(sel.begin(), sel.end(), back_inserter(sorted),
-			[&](AssDialogue *d) { return !d->Comment && styles.count(d->Style); });
-	}
+	auto valid_line = [&](const AssDialogue *d) { return !d->Comment && styles.count(d->Style); };
+	if (onlySelection->IsChecked())
+		boost::copy(c->selectionController->GetSelectedSet() | filtered(valid_line),
+		    back_inserter(sorted));
 	else {
-		transform(c->ass->Events.begin(), c->ass->Events.end(), back_inserter(sorted), cast<AssDialogue*>());
-		sorted.erase(boost::remove_if(sorted, bind(bad_line, &styles, _1)), sorted.end());
+		sorted.reserve(c->ass->Events.size());
+		boost::push_back(sorted, c->ass->Events | agi::address_of | filtered(valid_line));
 	}
 
 	// Check if rows are valid
@@ -324,7 +325,9 @@ std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
 		}
 	}
 
-	boost::sort(sorted, AssFile::CompStart);
+	boost::sort(sorted, [](const AssDialogue *a, const AssDialogue *b) {
+		return a->Start < b->Start;
+	});
 	return sorted;
 }
 
diff --git a/aegisub/src/export_fixstyle.cpp b/aegisub/src/export_fixstyle.cpp
index 04a71c672..54de56bdd 100644
--- a/aegisub/src/export_fixstyle.cpp
+++ b/aegisub/src/export_fixstyle.cpp
@@ -40,11 +40,8 @@
 #include "ass_dialogue.h"
 #include "compat.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <algorithm>
 #include <boost/algorithm/string/case_conv.hpp>
-#include <functional>
 #include <wx/intl.h>
 
 AssFixStylesFilter::AssFixStylesFilter()
@@ -53,12 +50,12 @@ AssFixStylesFilter::AssFixStylesFilter()
 }
 
 void AssFixStylesFilter::ProcessSubs(AssFile *subs, wxWindow *) {
-	std::vector<std::string> styles = subs->GetStyles();
-	for_each(begin(styles), end(styles), [](std::string& str) { boost::to_lower(str); });
+	auto styles = subs->GetStyles();
+	for (auto& str : styles) boost::to_lower(str);
 	sort(begin(styles), end(styles));
 
-	for (auto diag : subs->Events | agi::of_type<AssDialogue>()) {
-		if (!binary_search(begin(styles), end(styles), boost::to_lower_copy(diag->Style.get())))
-			diag->Style = "Default";
+	for (auto& diag : subs->Events) {
+		if (!binary_search(begin(styles), end(styles), boost::to_lower_copy(diag.Style.get())))
+			diag.Style = "Default";
 	}
 }
diff --git a/aegisub/src/export_framerate.cpp b/aegisub/src/export_framerate.cpp
index a827479fd..b7d4f1edd 100644
--- a/aegisub/src/export_framerate.cpp
+++ b/aegisub/src/export_framerate.cpp
@@ -201,20 +201,20 @@ void AssTransformFramerateFilter::TransformTimeTags(std::string const& name, Ass
 
 void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
 	if (!Input->IsLoaded() || !Output->IsLoaded()) return;
-	for (auto curDialogue : subs->Events | agi::of_type<AssDialogue>()) {
-		line = curDialogue;
+	for (auto& curDialogue : subs->Events) {
+		line = &curDialogue;
 		newK = 0;
 		oldK = 0;
-		newStart = trunc_cs(ConvertTime(curDialogue->Start));
-		newEnd = trunc_cs(ConvertTime(curDialogue->End) + 9);
+		newStart = trunc_cs(ConvertTime(curDialogue.Start));
+		newEnd = trunc_cs(ConvertTime(curDialogue.End) + 9);
 
 		// Process stuff
 		boost::ptr_vector<AssDialogueBlock> blocks;
 		for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
 			block->ProcessParameters(TransformTimeTags, this);
-		curDialogue->Start = newStart;
-		curDialogue->End = newEnd;
-		curDialogue->UpdateText(blocks);
+		curDialogue.Start = newStart;
+		curDialogue.End = newEnd;
+		curDialogue.UpdateText(blocks);
 	}
 }
 
diff --git a/aegisub/src/font_file_lister.cpp b/aegisub/src/font_file_lister.cpp
index f6d57d932..5bbfbb1e0 100644
--- a/aegisub/src/font_file_lister.cpp
+++ b/aegisub/src/font_file_lister.cpp
@@ -29,8 +29,6 @@
 #include "compat.h"
 #include "utils.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <algorithm>
 #include <tuple>
 #include <unicode/uchar.h>
@@ -182,17 +180,17 @@ std::vector<agi::fs::path> FontCollector::GetFontPaths(const AssFile *file) {
 
 	status_callback(_("Parsing file\n"), 0);
 
-	for (auto style : file->Styles | agi::of_type<const AssStyle>()) {
-		StyleInfo &info = styles[style->name];
-		info.facename = style->font;
-		info.bold     = style->bold;
-		info.italic   = style->italic;
-		used_styles[info].styles.insert(style->name);
+	for (auto const& style : file->Styles) {
+		StyleInfo &info = styles[style.name];
+		info.facename = style.font;
+		info.bold     = style.bold;
+		info.italic   = style.italic;
+		used_styles[info].styles.insert(style.name);
 	}
 
 	int index = 0;
-	for (auto diag : file->Events | agi::of_type<const AssDialogue>())
-		ProcessDialogueLine(diag, ++index);
+	for (auto const& diag : file->Events)
+		ProcessDialogueLine(&diag, ++index);
 
 	status_callback(_("Searching for font files\n"), 0);
 	for_each(used_styles.begin(), used_styles.end(), bind(&FontCollector::ProcessChunk, this, _1));
diff --git a/aegisub/src/resolution_resampler.cpp b/aegisub/src/resolution_resampler.cpp
index 069e0f7be..c1179fe11 100644
--- a/aegisub/src/resolution_resampler.cpp
+++ b/aegisub/src/resolution_resampler.cpp
@@ -125,32 +125,33 @@ namespace {
 			cur->Set<int>((cur->Get<int>() + shift) * resizer + 0.5);
 	}
 
-	void resample_line(resample_state *state, AssEntry &line) {
-		AssDialogue *diag = dynamic_cast<AssDialogue*>(&line);
-		if (diag && !(diag->Comment && (boost::starts_with(diag->Effect.get(), "template") || boost::starts_with(diag->Effect.get(), "code")))) {
-			boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
+	void resample_line(resample_state *state, AssDialogue &diag) {
+		if (diag.Comment && (boost::starts_with(diag.Effect.get(), "template") || boost::starts_with(diag.Effect.get(), "code")))
+			return;
 
-			for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
-				block->ProcessParameters(resample_tags, state);
+		boost::ptr_vector<AssDialogueBlock> blocks(diag.ParseTags());
 
-			for (auto drawing : blocks | agi::of_type<AssDialogueBlockDrawing>())
-				drawing->text = transform_drawing(drawing->text, state->margin[LEFT], state->margin[TOP], state->rx, state->ry);
+		for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
+			block->ProcessParameters(resample_tags, state);
 
-			for (size_t i = 0; i < 3; ++i)
-				diag->Margin[i] = int((diag->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5);
+		for (auto drawing : blocks | agi::of_type<AssDialogueBlockDrawing>())
+			drawing->text = transform_drawing(drawing->text, state->margin[LEFT], state->margin[TOP], state->rx, state->ry);
 
-			diag->UpdateText(blocks);
-		}
-		else if (AssStyle *style = dynamic_cast<AssStyle*>(&line)) {
-			style->fontsize = int(style->fontsize * state->ry + 0.5);
-			style->outline_w *= state->ry;
-			style->shadow_w *= state->ry;
-			style->spacing *= state->rx;
-			style->scalex *= state->ar;
-			for (int i = 0; i < 3; i++)
-				style->Margin[i] = int((style->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5);
-			style->UpdateData();
-		}
+		for (size_t i = 0; i < 3; ++i)
+			diag.Margin[i] = int((diag.Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5);
+
+		diag.UpdateText(blocks);
+	}
+
+	void resample_style(resample_state *state, AssStyle &style) {
+		style.fontsize = int(style.fontsize * state->ry + 0.5);
+		style.outline_w *= state->ry;
+		style.shadow_w *= state->ry;
+		style.spacing *= state->rx;
+		style.scalex *= state->ar;
+		for (int i = 0; i < 3; i++)
+			style.Margin[i] = int((style.Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5);
+		style.UpdateData();
 	}
 }
 
@@ -173,7 +174,7 @@ void ResampleResolution(AssFile *ass, ResampleSettings const& settings) {
 		state.ar = state.rx / state.ry;
 
 	for (auto& line : ass->Styles)
-		resample_line(&state, line);
+		resample_style(&state, line);
 	for (auto& line : ass->Events)
 		resample_line(&state, line);
 
diff --git a/aegisub/src/search_replace_engine.cpp b/aegisub/src/search_replace_engine.cpp
index b821e08dd..e28598a71 100644
--- a/aegisub/src/search_replace_engine.cpp
+++ b/aegisub/src/search_replace_engine.cpp
@@ -24,7 +24,6 @@
 #include "selection_controller.h"
 #include "text_selection_controller.h"
 
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
 #include <boost/locale.hpp>
@@ -269,17 +268,15 @@ bool SearchReplaceEngine::FindReplace(bool replace) {
 	bool selection_only = sel.size() > 1 && settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
 
 	do {
-		AssDialogue *diag = dynamic_cast<AssDialogue*>(&*it);
-		if (!diag) continue;
-		if (selection_only && !sel.count(diag)) continue;
-		if (settings.ignore_comments && diag->Comment) continue;
+		if (selection_only && !sel.count(&*it)) continue;
+		if (settings.ignore_comments && it->Comment) continue;
 
-		if (MatchState ms = matches(diag, pos)) {
+		if (MatchState ms = matches(&*it, pos)) {
 			if (selection_only)
 				// We're cycling through the selection, so don't muck with it
-				context->selectionController->SetActiveLine(diag);
+				context->selectionController->SetActiveLine(&*it);
 			else
-				context->selectionController->SetSelectionAndActive({ diag }, diag);
+				context->selectionController->SetSelectionAndActive({ &*it }, &*it);
 
 			if (settings.field == SearchReplaceSettings::Field::TEXT)
 				context->textSelectionController->SetSelection(ms.start, ms.end);
@@ -307,13 +304,13 @@ bool SearchReplaceEngine::ReplaceAll() {
 	SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
 	bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
 
-	for (auto diag : context->ass->Events | agi::of_type<AssDialogue>()) {
-		if (selection_only && !sel.count(diag)) continue;
-		if (settings.ignore_comments && diag->Comment) continue;
+	for (auto& diag : context->ass->Events) {
+		if (selection_only && !sel.count(&diag)) continue;
+		if (settings.ignore_comments && diag.Comment) continue;
 
 		if (settings.use_regex) {
-			if (MatchState ms = matches(diag, 0)) {
-				auto& diag_field = diag->*get_dialogue_field(settings.field);
+			if (MatchState ms = matches(&diag, 0)) {
+				auto& diag_field = diag.*get_dialogue_field(settings.field);
 				std::string const& text = diag_field.get();
 				count += distance(
 					boost::u32regex_iterator<std::string::const_iterator>(begin(text), end(text), *ms.re),
@@ -324,9 +321,9 @@ bool SearchReplaceEngine::ReplaceAll() {
 		}
 
 		size_t pos = 0;
-		while (MatchState ms = matches(diag, pos)) {
+		while (MatchState ms = matches(&diag, pos)) {
 			++count;
-			Replace(diag, ms);
+			Replace(&diag, ms);
 			pos = ms.end;
 		}
 	}
diff --git a/aegisub/src/subs_controller.cpp b/aegisub/src/subs_controller.cpp
index d1835f9b6..a82846362 100644
--- a/aegisub/src/subs_controller.cpp
+++ b/aegisub/src/subs_controller.cpp
@@ -70,26 +70,22 @@ struct SubsController::UndoInfo {
 	: undo_description(d), commit_id(commit_id)
 	{
 		script_info.reserve(c->ass->Info.size());
-		for (auto const& line : c->ass->Info) {
-			auto info = static_cast<const AssInfo *>(&line);
-			script_info.emplace_back(info->Key(), info->Value());
-		}
+		for (auto const& info : c->ass->Info)
+			script_info.emplace_back(info.Key(), info.Value());
 
 		styles.reserve(c->ass->Styles.size());
-		for (auto const& line : c->ass->Styles)
-			styles.push_back(static_cast<AssStyle const&>(line));
+		styles.assign(c->ass->Styles.begin(), c->ass->Styles.end());
 
 		events.reserve(c->ass->Events.size());
-		for (auto const& line : c->ass->Events)
-			events.push_back(static_cast<AssDialogue const&>(line));
+		events.assign(c->ass->Events.begin(), c->ass->Events.end());
 
 		for (auto const& line : c->ass->Attachments) {
 			switch (line.Group()) {
 			case AssEntryGroup::FONT:
-				fonts.push_back(static_cast<AssAttachment const&>(line));
+				fonts.push_back(line);
 				break;
 			case AssEntryGroup::GRAPHIC:
-				graphics.push_back(static_cast<AssAttachment const&>(line));
+				graphics.push_back(line);
 				break;
 			default:
 				assert(false);
@@ -364,10 +360,9 @@ void SubsController::OnCommit(AssFileCommit c) {
 	if (commit_id == *c.commit_id+1 && redo_stack.empty() && saved_commit_id+1 != commit_id && autosaved_commit_id+1 != commit_id) {
 		// If only one line changed just modify it instead of copying the file
 		if (c.single_line && c.single_line->Group() == AssEntryGroup::DIALOGUE) {
-			auto src_diag = static_cast<const AssDialogue *>(c.single_line);
 			for (auto& diag : undo_stack.back().events) {
-				if (diag.Id == src_diag->Id) {
-					diag = *src_diag;
+				if (diag.Id == c.single_line->Id) {
+					diag = *c.single_line;
 					break;
 				}
 			}
diff --git a/aegisub/src/subs_controller.h b/aegisub/src/subs_controller.h
index f0c5fbd99..f85571603 100644
--- a/aegisub/src/subs_controller.h
+++ b/aegisub/src/subs_controller.h
@@ -23,7 +23,6 @@
 #include <wx/timer.h>
 
 class AssDialogue;
-class AssEntry;
 class AssFile;
 struct AssFileCommit;
 template<typename T> class SelectionController;
diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp
index e365b7a20..012d55a4c 100644
--- a/aegisub/src/subs_edit_box.cpp
+++ b/aegisub/src/subs_edit_box.cpp
@@ -56,7 +56,6 @@
 #include "video_context.h"
 
 #include <libaegisub/dispatch.h>
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
 #include <functional>
@@ -344,7 +343,7 @@ void SubsEditBox::PopulateList(wxComboBox *combo, boost::flyweight<std::string>
 
 	std::unordered_set<std::string> values;
 	for (auto const& line : c->ass->Events) {
-		auto const& value = static_cast<const AssDialogue *>(&line)->*field;
+		auto const& value = line.*field;
 		if (!value.get().empty())
 			values.insert(value);
 	}
diff --git a/aegisub/src/subtitle_format.cpp b/aegisub/src/subtitle_format.cpp
index ec599d648..36355dc69 100644
--- a/aegisub/src/subtitle_format.cpp
+++ b/aegisub/src/subtitle_format.cpp
@@ -57,7 +57,6 @@
 #include "video_context.h"
 
 #include <libaegisub/fs.h>
-#include <libaegisub/of_type_adaptor.h>
 
 #include <algorithm>
 #include <boost/algorithm/string/join.hpp>
@@ -94,13 +93,12 @@ bool SubtitleFormat::CanSave(const AssFile *subs) const {
 
 	std::string defstyle = AssStyle().GetEntryData();
 	for (auto const& line : subs->Styles) {
-		if (static_cast<const AssStyle *>(&line)->GetEntryData() != defstyle)
+		if (line.GetEntryData() != defstyle)
 			return false;
 	}
 
 	for (auto const& line : subs->Events) {
-		auto diag = static_cast<const AssDialogue *>(&line);
-		if (diag->GetStrippedText() != diag->Text)
+		if (line.GetStrippedText() != line.Text)
 			return false;
 	}
 
@@ -173,13 +171,13 @@ agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) {
 }
 
 void SubtitleFormat::StripTags(AssFile &file) {
-	for (auto current : file.Events | agi::of_type<AssDialogue>())
-		current->StripTags();
+	for (auto& current : file.Events)
+		current.StripTags();
 }
 
 void SubtitleFormat::ConvertNewlines(AssFile &file, std::string const& newline, bool mergeLineBreaks) {
-	for (auto current : file.Events | agi::of_type<AssDialogue>()) {
-		std::string repl = current->Text;
+	for (auto& current : file.Events) {
+		std::string repl = current.Text;
 		boost::replace_all(repl, "\\h", " ");
 		boost::ireplace_all(repl, "\\n", newline);
 		if (mergeLineBreaks) {
@@ -188,39 +186,35 @@ void SubtitleFormat::ConvertNewlines(AssFile &file, std::string const& newline,
 			while ((pos = repl.find(dbl, pos)) != std::string::npos)
 				boost::replace_all(repl, dbl, newline);
 		}
-		current->Text = repl;
+		current.Text = repl;
 	}
 }
 
 void SubtitleFormat::StripComments(AssFile &file) {
-	file.Events.remove_and_dispose_if([](AssEntry const& e) {
-		const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&e);
-		return diag && (diag->Comment || diag->Text.get().empty());
-	}, [](AssEntry *e) { delete e; });
+	file.Events.remove_and_dispose_if([](AssDialogue const& diag) {
+		return diag.Comment || diag.Text.get().empty();
+	}, [](AssDialogue *e) { delete e; });
 }
 
-static bool dialog_start_lt(AssEntry &pos, AssDialogue *to_insert) {
-	AssDialogue *diag = dynamic_cast<AssDialogue*>(&pos);
-	return diag && diag->Start > to_insert->Start;
+static bool dialog_start_lt(AssDialogue &pos, AssDialogue *to_insert) {
+	return pos.Start > to_insert->Start;
 }
 
 /// @brief Split and merge lines so there are no overlapping lines
 ///
 /// Algorithm described at http://devel.aegisub.org/wiki/Technical/SplitMerge
 void SubtitleFormat::RecombineOverlaps(AssFile &file) {
-	entryIter cur, next = file.Events.begin();
-	cur = next++;
+	auto next = file.Events.begin();
+	auto cur = next++;
 
 	for (; next != file.Events.end(); cur = next++) {
-		AssDialogue *prevdlg = dynamic_cast<AssDialogue*>(&*cur);
-		AssDialogue *curdlg = dynamic_cast<AssDialogue*>(&*next);
-
-		if (!curdlg || !prevdlg) continue;
+		AssDialogue *prevdlg = &*cur;
+		AssDialogue *curdlg = &*next;
 		if (prevdlg->End <= curdlg->Start) continue;
 
 		// Use names like in the algorithm description and prepare for erasing
 		// old dialogues from the list
-		entryIter prev = cur;
+		auto prev = cur;
 		cur = next;
 		next++;
 
@@ -280,20 +274,17 @@ void SubtitleFormat::RecombineOverlaps(AssFile &file) {
 
 /// @brief Merge identical lines that follow each other
 void SubtitleFormat::MergeIdentical(AssFile &file) {
-	entryIter cur, next = file.Events.begin();
-	cur = next++;
+	auto next = file.Events.begin();
+	auto cur = next++;
 
 	for (; next != file.Events.end(); cur = next++) {
-		AssDialogue *curdlg = dynamic_cast<AssDialogue*>(&*cur);
-		AssDialogue *nextdlg = dynamic_cast<AssDialogue*>(&*next);
-
-		if (curdlg && nextdlg && curdlg->End == nextdlg->Start && curdlg->Text == nextdlg->Text) {
+		if (cur->End == next->Start && cur->Text == next->Text) {
 			// Merge timing
-			nextdlg->Start = std::min(nextdlg->Start, curdlg->Start);
-			nextdlg->End = std::max(nextdlg->End, curdlg->End);
+			next->Start = std::min(next->Start, cur->Start);
+			next->End = std::max(next->End, cur->End);
 
 			// Remove duplicate line
-			delete curdlg;
+			delete &*cur;
 		}
 	}
 }
diff --git a/aegisub/src/subtitle_format.h b/aegisub/src/subtitle_format.h
index 3a66f93a8..5d4f847ad 100644
--- a/aegisub/src/subtitle_format.h
+++ b/aegisub/src/subtitle_format.h
@@ -40,7 +40,6 @@
 #include <string>
 #include <vector>
 
-class AssEntry;
 class AssFile;
 namespace agi { namespace vfr { class Framerate; } }
 
diff --git a/aegisub/src/subtitle_format_ass.cpp b/aegisub/src/subtitle_format_ass.cpp
index 898573037..cda8c761b 100644
--- a/aegisub/src/subtitle_format_ass.cpp
+++ b/aegisub/src/subtitle_format_ass.cpp
@@ -1,42 +1,28 @@
-// Copyright (c) 2006, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
-/// @file subtitle_format_ass.cpp
-/// @brief Reading/writing of SSA-lineage subtitles
-/// @ingroup subtitle_io
-///
-
 #include "config.h"
 
 #include "subtitle_format_ass.h"
 
+#include "ass_attachment.h"
+#include "ass_dialogue.h"
+#include "ass_info.h"
 #include "ass_file.h"
+#include "ass_style.h"
 #include "ass_parser.h"
 #include "text_file_reader.h"
 #include "text_file_writer.h"
@@ -52,17 +38,11 @@ AssSubtitleFormat::AssSubtitleFormat()
 }
 
 std::vector<std::string> AssSubtitleFormat::GetReadWildcards() const {
-	std::vector<std::string> formats;
-	formats.push_back("ass");
-	formats.push_back("ssa");
-	return formats;
+	return {"ass", "ssa"};
 }
 
 std::vector<std::string> AssSubtitleFormat::GetWriteWildcards() const {
-	std::vector<std::string> formats;
-	formats.push_back("ass");
-	formats.push_back("ssa");
-	return formats;
+	return {"ass", "ssa"};
 }
 
 void AssSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
@@ -87,7 +67,8 @@ void AssSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename,
 #define LINEBREAK "\n"
 #endif
 
-static inline std::string format(AssEntryGroup group, bool ssa) {
+namespace {
+std::string format(AssEntryGroup group, bool ssa) {
 	if (group == AssEntryGroup::DIALOGUE) {
 		if (ssa)
 			return "Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" LINEBREAK;
@@ -105,17 +86,22 @@ static inline std::string format(AssEntryGroup group, bool ssa) {
 	return "";
 }
 
-void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
-	TextFileWriter file(filename, encoding);
-
-	file.WriteLineToFile("[Script Info]");
-	file.WriteLineToFile(std::string("; Script generated by Aegisub ") + GetAegisubLongVersionString());
-	file.WriteLineToFile("; http://www.aegisub.org/");
-
-	bool ssa = agi::fs::HasExtension(filename, "ssa");
+struct Writer {
+	TextFileWriter file;
+	bool ssa;
 	AssEntryGroup group = AssEntryGroup::INFO;
 
-	auto write = [&](EntryList const& list) {
+	Writer(agi::fs::path const& filename, std::string const& encoding)
+	: file(filename, encoding)
+	, ssa(agi::fs::HasExtension(filename, "ssa"))
+	{
+		file.WriteLineToFile("[Script Info]");
+		file.WriteLineToFile(std::string("; Script generated by Aegisub ") + GetAegisubLongVersionString());
+		file.WriteLineToFile("; http://www.aegisub.org/");
+	}
+
+	template<typename T>
+	void Write(T const& list) {
 		for (auto const& line : list) {
 			if (line.Group() != group) {
 				// Add a blank line between each group
@@ -129,10 +115,15 @@ void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filen
 
 			file.WriteLineToFile(ssa ? line.GetSSAText() : line.GetEntryData());
 		}
-	};
+	}
+};
+}
+
+void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
+	Writer writer(filename, encoding);
 
-	write(src->Info);
-	write(src->Styles);
-	write(src->Attachments);
-	write(src->Events);
+	writer.Write(src->Info);
+	writer.Write(src->Styles);
+	writer.Write(src->Attachments);
+	writer.Write(src->Events);
 }
diff --git a/aegisub/src/subtitle_format_ebu3264.cpp b/aegisub/src/subtitle_format_ebu3264.cpp
index 79411960f..8e73df9e7 100644
--- a/aegisub/src/subtitle_format_ebu3264.cpp
+++ b/aegisub/src/subtitle_format_ebu3264.cpp
@@ -37,7 +37,6 @@
 #include <libaegisub/exception.h>
 #include <libaegisub/io.h>
 #include <libaegisub/line_wrap.h>
-#include <libaegisub/of_type_adaptor.h>
 
 #include <boost/algorithm/string/replace.hpp>
 #include <fstream>
@@ -373,7 +372,7 @@ namespace
 		subs_list.reserve(copy.Events.size());
 
 		// convert to intermediate format
-		for (auto line : copy.Events | agi::of_type<AssDialogue>())
+		for (auto& line : copy.Events)
 		{
 			// add a new subtitle and work on it
 			subs_list.emplace_back();
@@ -385,19 +384,19 @@ namespace
 			imline.cumulative_status = EbuSubtitle::NotCumulative;
 
 			// convert times
-			imline.time_in = fps.FrameAtTime(line->Start) + timecode_bias;
-			imline.time_out = fps.FrameAtTime(line->End) + timecode_bias;
+			imline.time_in = fps.FrameAtTime(line.Start) + timecode_bias;
+			imline.time_out = fps.FrameAtTime(line.End) + timecode_bias;
 			if (export_settings.inclusive_end_times)
 				// cheap and possibly wrong way to ensure exclusive times, subtract one frame from end time
 				imline.time_out -= 1;
 
 			// convert alignment from style
-			AssStyle *style = copy.GetStyle(line->Style);
+			AssStyle *style = copy.GetStyle(line.Style);
 			if (!style)
 				style = &default_style;
 
 			// add text, translate formatting
-			imline.SetTextFromAss(line, style->underline, style->italic, style->alignment, line_wrap_type);
+			imline.SetTextFromAss(&line, style->underline, style->italic, style->alignment, line_wrap_type);
 
 			// line breaking handling
 			if (export_settings.line_wrapping_mode == EbuExportSettings::AutoWrap)
@@ -407,7 +406,7 @@ namespace
 			else if (!imline.CheckLineLengths(export_settings.max_line_length))
 			{
 				if (export_settings.line_wrapping_mode == EbuExportSettings::AbortOverLength)
-					throw Ebu3264SubtitleFormat::ConversionFailed(from_wx(wxString::Format(_("Line over maximum length: %s"), line->Text.get())), nullptr);
+					throw Ebu3264SubtitleFormat::ConversionFailed(from_wx(wxString::Format(_("Line over maximum length: %s"), line.Text.get())), nullptr);
 				else // skip over-long lines
 					subs_list.pop_back();
 			}
diff --git a/aegisub/src/subtitle_format_encore.cpp b/aegisub/src/subtitle_format_encore.cpp
index d710345bc..aa2dc1f2f 100644
--- a/aegisub/src/subtitle_format_encore.cpp
+++ b/aegisub/src/subtitle_format_encore.cpp
@@ -40,8 +40,6 @@
 #include "ass_file.h"
 #include "text_file_writer.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/filesystem/path.hpp>
 #include <boost/format.hpp>
@@ -77,6 +75,6 @@ void EncoreSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& fi
 	// Write lines
 	int i = 0;
 	TextFileWriter file(filename, "UTF-8");
-	for (auto current : copy.Events | agi::of_type<AssDialogue>())
-		file.WriteLineToFile(str(boost::format("%i %s %s %s") % ++i % ft.ToSMPTE(current->Start) % ft.ToSMPTE(current->End) % current->Text));
+	for (auto const& current : copy.Events)
+		file.WriteLineToFile(str(boost::format("%i %s %s %s") % ++i % ft.ToSMPTE(current.Start) % ft.ToSMPTE(current.End) % current.Text));
 }
diff --git a/aegisub/src/subtitle_format_microdvd.cpp b/aegisub/src/subtitle_format_microdvd.cpp
index c14553da0..a579658b4 100644
--- a/aegisub/src/subtitle_format_microdvd.cpp
+++ b/aegisub/src/subtitle_format_microdvd.cpp
@@ -44,7 +44,6 @@
 #include "video_context.h"
 
 #include <libaegisub/fs.h>
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
 #include <boost/algorithm/string/replace.hpp>
@@ -143,10 +142,10 @@ void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const&
 		file.WriteLineToFile(str(boost::format("{1}{1}%.6f") % fps.FPS()));
 
 	// Write lines
-	for (auto current : copy.Events | agi::of_type<AssDialogue>()) {
-		int start = fps.FrameAtTime(current->Start, agi::vfr::START);
-		int end = fps.FrameAtTime(current->End, agi::vfr::END);
+	for (auto const& current : copy.Events) {
+		int start = fps.FrameAtTime(current.Start, agi::vfr::START);
+		int end = fps.FrameAtTime(current.End, agi::vfr::END);
 
-		file.WriteLineToFile(str(boost::format("{%i}{%i}%s") % start % end % boost::replace_all_copy(current->Text.get(), "\\N", "|")));
+		file.WriteLineToFile(str(boost::format("{%i}{%i}%s") % start % end % boost::replace_all_copy(current.Text.get(), "\\N", "|")));
 	}
 }
diff --git a/aegisub/src/subtitle_format_srt.cpp b/aegisub/src/subtitle_format_srt.cpp
index c5078b4eb..1d8f0341e 100644
--- a/aegisub/src/subtitle_format_srt.cpp
+++ b/aegisub/src/subtitle_format_srt.cpp
@@ -480,10 +480,10 @@ void SRTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filen
 
 	// Write lines
 	int i=0;
-	for (auto current : copy.Events | agi::of_type<AssDialogue>()) {
+	for (auto const& current : copy.Events) {
 		file.WriteLineToFile(std::to_string(++i));
-		file.WriteLineToFile(WriteSRTTime(current->Start) + " --> " + WriteSRTTime(current->End));
-		file.WriteLineToFile(ConvertTags(current));
+		file.WriteLineToFile(WriteSRTTime(current.Start) + " --> " + WriteSRTTime(current.End));
+		file.WriteLineToFile(ConvertTags(&current));
 		file.WriteLineToFile("");
 	}
 }
@@ -496,13 +496,12 @@ bool SRTSubtitleFormat::CanSave(const AssFile *file) const {
 
 	std::string defstyle = AssStyle().GetEntryData();
 	for (auto const& line : file->Styles) {
-		if (static_cast<const AssStyle *>(&line)->GetEntryData() != defstyle)
+		if (line.GetEntryData() != defstyle)
 			return false;
 	}
 
 	for (auto const& line : file->Events) {
-		auto diag = static_cast<const AssDialogue *>(&line);
-		boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
+		boost::ptr_vector<AssDialogueBlock> blocks(line.ParseTags());
 		for (auto ovr : blocks | agi::of_type<AssDialogueBlockOverride>()) {
 			// Verify that all overrides used are supported
 			for (auto const& tag : ovr->Tags) {
diff --git a/aegisub/src/subtitle_format_transtation.cpp b/aegisub/src/subtitle_format_transtation.cpp
index 4cb3d58aa..285551536 100644
--- a/aegisub/src/subtitle_format_transtation.cpp
+++ b/aegisub/src/subtitle_format_transtation.cpp
@@ -44,8 +44,6 @@
 #include "ass_time.h"
 #include "text_file_writer.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/filesystem/path.hpp>
 #include <boost/format.hpp>
@@ -76,14 +74,14 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path cons
 
 	SmpteFormatter ft(fps);
 	TextFileWriter file(filename, encoding);
-	AssDialogue *prev = nullptr;
-	for (auto cur : copy.Events | agi::of_type<AssDialogue>()) {
+	const AssDialogue *prev = nullptr;
+	for (auto const& cur : copy.Events) {
 		if (prev) {
-			file.WriteLineToFile(ConvertLine(&copy, prev, fps, ft, cur->Start));
+			file.WriteLineToFile(ConvertLine(&copy, prev, fps, ft, cur.Start));
 			file.WriteLineToFile("");
 		}
 
-		prev = cur;
+		prev = &cur;
 	}
 
 	// flush last line
@@ -94,7 +92,7 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path cons
 	file.WriteLineToFile("SUB[");
 }
 
-std::string TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const {
+std::string TranStationSubtitleFormat::ConvertLine(AssFile *file, const AssDialogue *current, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const {
 	int valign = 0;
 	const char *halign = " "; // default is centered
 	const char *type = "N"; // no special style
diff --git a/aegisub/src/subtitle_format_transtation.h b/aegisub/src/subtitle_format_transtation.h
index 174e2e197..5740ce405 100644
--- a/aegisub/src/subtitle_format_transtation.h
+++ b/aegisub/src/subtitle_format_transtation.h
@@ -38,7 +38,7 @@ class AssDialogue;
 class SmpteFormatter;
 
 class TranStationSubtitleFormat : public SubtitleFormat {
-	std::string ConvertLine(AssFile *file, AssDialogue *line, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const;
+	std::string ConvertLine(AssFile *file, const AssDialogue *line, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const;
 
 public:
 	TranStationSubtitleFormat();
diff --git a/aegisub/src/subtitle_format_ttxt.cpp b/aegisub/src/subtitle_format_ttxt.cpp
index 28a293d98..0532adc57 100644
--- a/aegisub/src/subtitle_format_ttxt.cpp
+++ b/aegisub/src/subtitle_format_ttxt.cpp
@@ -44,7 +44,6 @@
 #include "compat.h"
 #include "options.h"
 
-#include <libaegisub/of_type_adaptor.h>
 #include <boost/range/adaptor/reversed.hpp>
 
 DEFINE_SIMPLE_EXCEPTION(TTXTParseError, SubtitleFormatParseError, "subtitle_io/parse/ttxt")
@@ -177,9 +176,9 @@ void TTXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& file
 
 	// Create lines
 	const AssDialogue *prev = nullptr;
-	for (auto current : copy.Events | agi::of_type<AssDialogue>()) {
-		WriteLine(root, prev, current);
-		prev = current;
+	for (auto const& current : copy.Events) {
+		WriteLine(root, prev, &current);
+		prev = &current;
 	}
 
 	// Save XML
@@ -264,8 +263,8 @@ void TTXTSubtitleFormat::ConvertToTTXT(AssFile &file) const {
 
 	// Find last line
 	AssTime lastTime;
-	for (auto line : file.Events | boost::adaptors::reversed | agi::of_type<AssDialogue>()) {
-		lastTime = line->End;
+	for (auto const& line : file.Events | boost::adaptors::reversed) {
+		lastTime = line.End;
 		break;
 	}
 
diff --git a/aegisub/src/subtitle_format_txt.cpp b/aegisub/src/subtitle_format_txt.cpp
index 3ced45aec..9e4d7c326 100644
--- a/aegisub/src/subtitle_format_txt.cpp
+++ b/aegisub/src/subtitle_format_txt.cpp
@@ -45,8 +45,6 @@
 #include "utils.h"
 #include "version.h"
 
-#include <libaegisub/of_type_adaptor.h>
-
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/trim.hpp>
 
@@ -131,10 +129,10 @@ void TXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filen
 	size_t num_actor_names = 0, num_dialogue_lines = 0;
 
 	// Detect number of lines with Actor field filled out
-	for (auto dia : src->Events | agi::of_type<AssDialogue>()) {
-		if (!dia->Comment) {
+	for (auto const& dia : src->Events) {
+		if (!dia.Comment) {
 			num_dialogue_lines++;
-			if (!dia->Actor.get().empty())
+			if (!dia.Actor.get().empty())
 				num_actor_names++;
 		}
 	}
@@ -147,16 +145,16 @@ void TXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filen
 	file.WriteLineToFile(std::string("# Exported by Aegisub ") + GetAegisubShortVersionString());
 
 	// Write the file
-	for (auto dia : src->Events | agi::of_type<AssDialogue>()) {
+	for (auto const& dia : src->Events) {
 		std::string out_line;
 
-		if (dia->Comment)
+		if (dia.Comment)
 			out_line = "# ";
 
 		if (write_actors)
-			out_line += dia->Actor.get() + ": ";
+			out_line += dia.Actor.get() + ": ";
 
-		std::string out_text = strip_formatting ? dia->GetStrippedText() : dia->Text;
+		std::string out_text = strip_formatting ? dia.GetStrippedText() : dia.Text;
 		out_line += out_text;
 
 		if (!out_text.empty())
diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp
index 2e4e66008..1557800c9 100644
--- a/aegisub/src/subtitles_provider_libass.cpp
+++ b/aegisub/src/subtitles_provider_libass.cpp
@@ -41,7 +41,10 @@
 #include <libaegisub/util_osx.h>
 #endif
 
+#include "ass_info.h"
+#include "ass_dialogue.h"
 #include "ass_file.h"
+#include "ass_style.h"
 #include "dialog_progress.h"
 #include "frame_main.h"
 #include "main.h"
@@ -112,28 +115,35 @@ LibassSubtitlesProvider::~LibassSubtitlesProvider() {
 	if (ass_renderer) ass_renderer_done(ass_renderer);
 }
 
-void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) {
-	std::vector<char> data;
-	data.clear();
-	data.reserve(0x4000);
-
-	AssEntryGroup group = AssEntryGroup::GROUP_MAX;
-	auto write_group = [&](EntryList const& list) {
-		for (auto const& line : list) {
-			if (group != line.Group()) {
-				group = line.Group();
-				boost::push_back(data, line.GroupHeader() + "\r\n");
+namespace {
+	struct Writer {
+		std::vector<char> data;
+		AssEntryGroup group = AssEntryGroup::GROUP_MAX;
+
+		template<typename T>
+		void Write(T const& list) {
+			for (auto const& line : list) {
+				if (group != line.Group()) {
+					group = line.Group();
+					boost::push_back(data, line.GroupHeader() + "\r\n");
+				}
+				boost::push_back(data, line.GetEntryData() + "\r\n");
 			}
-			boost::push_back(data, line.GetEntryData() + "\r\n");
 		}
 	};
+}
+
+void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) {
+	Writer writer;
+
+	writer.data.reserve(0x4000);
 
-	write_group(subs->Info);
-	write_group(subs->Styles);
-	write_group(subs->Events);
+	writer.Write(subs->Info);
+	writer.Write(subs->Styles);
+	writer.Write(subs->Events);
 
 	if (ass_track) ass_free_track(ass_track);
-	ass_track = ass_read_memory(library, &data[0], data.size(), nullptr);
+	ass_track = ass_read_memory(library, &writer.data[0], writer.data.size(), nullptr);
 	if (!ass_track) throw "libass failed to load subtitles.";
 }
 
diff --git a/aegisub/src/threaded_frame_source.cpp b/aegisub/src/threaded_frame_source.cpp
index 87b37a297..52f48366d 100644
--- a/aegisub/src/threaded_frame_source.cpp
+++ b/aegisub/src/threaded_frame_source.cpp
@@ -28,6 +28,7 @@
 #include "video_frame.h"
 #include "video_provider_manager.h"
 
+#include <libaegisub/address_of_adaptor.h>
 #include <libaegisub/dispatch.h>
 
 #include <boost/range/adaptor/indirected.hpp>
@@ -70,12 +71,10 @@ std::shared_ptr<VideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, dou
 				// Copying a nontrivially sized AssFile is fairly slow, so
 				// instead muck around with its innards to just temporarily
 				// remove the non-visible lines without deleting them
-				std::deque<AssEntry*> full;
-				for (auto& line : subs->Events)
-					full.push_back(&line);
-				subs->Events.remove_if([=](AssEntry const& e) -> bool {
-					const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&e);
-					return diag && (diag->Start > time || diag->End <= time);
+				std::deque<AssDialogue*> full;
+				boost::push_back(full, subs->Events | agi::address_of);
+				subs->Events.remove_if([=](AssDialogue const& diag) {
+					return diag.Start > time || diag.End <= time;
 				});
 
 				try {
@@ -133,12 +132,12 @@ void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
 	});
 }
 
-void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<const AssEntry*> const& changes) throw() {
+void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> const& changes) throw() {
 	uint_fast32_t req_version = ++version;
 
 	// Copy just the lines which were changed, then replace the lines at the
 	// same indices in the worker's copy of the file with the new entries
-	std::deque<std::pair<size_t, AssEntry*>> changed;
+	std::deque<std::pair<size_t, AssDialogue*>> changed;
 	size_t i = 0;
 	for (auto const& e : new_subs->Events) {
 		if (changes.count(&e))
diff --git a/aegisub/src/threaded_frame_source.h b/aegisub/src/threaded_frame_source.h
index fa7223245..fbb08c914 100644
--- a/aegisub/src/threaded_frame_source.h
+++ b/aegisub/src/threaded_frame_source.h
@@ -29,7 +29,7 @@
 
 #include <wx/event.h>
 
-class AssEntry;
+class AssDialogue;
 class AssFile;
 class SubtitlesProvider;
 class VideoProvider;
@@ -84,7 +84,7 @@ public:
 	///
 	/// This function only supports changes to existing lines, and not
 	/// insertions or deletions.
-	void UpdateSubtitles(const AssFile *subs, std::set<const AssEntry *> const& changes) throw();
+	void UpdateSubtitles(const AssFile *subs, std::set<const AssDialogue *> const& changes) throw();
 
 	/// @brief Queue a request for a frame
 	/// @brief frame Frame number
diff --git a/aegisub/src/utils.h b/aegisub/src/utils.h
index dd5a7edb5..2cbfb555f 100644
--- a/aegisub/src/utils.h
+++ b/aegisub/src/utils.h
@@ -115,19 +115,6 @@ void SetClipboard(wxBitmap const& new_value);
 
 #define countof(array) (sizeof(array) / sizeof(array[0]))
 
-template<class Out>
-struct cast {
-	template<class In>
-	Out operator()(In *in) const {
-		return dynamic_cast<Out>(in);
-	}
-
-	template<class In>
-	Out operator()(In &in) const {
-		return dynamic_cast<Out>(&in);
-	}
-};
-
 wxString FontFace(std::string opt_prefix);
 
 agi::fs::path OpenFileSelector(wxString const& message, std::string const& option_name, std::string const& default_filename, std::string const& default_extension, wxString const& wildcard, wxWindow *parent);
diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp
index 4560e61d5..31da0be64 100644
--- a/aegisub/src/video_context.cpp
+++ b/aegisub/src/video_context.cpp
@@ -232,7 +232,7 @@ void VideoContext::Reload() {
 	}
 }
 
-void VideoContext::OnSubtitlesCommit(int type, std::set<const AssEntry *> const& changed) {
+void VideoContext::OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed) {
 	if (!IsLoaded()) return;
 
 	if (changed.empty() || no_amend)
@@ -436,7 +436,7 @@ void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
 		ovr_fps = agi::vfr::Framerate(filename);
 		timecodes_filename = filename;
 		config::mru->Add("Timecodes", filename);
-		OnSubtitlesCommit(0, std::set<const AssEntry*>());
+		OnSubtitlesCommit(0, std::set<const AssDialogue*>());
 		TimecodesOpen(ovr_fps);
 	}
 	catch (agi::fs::FileSystemError const& err) {
@@ -460,7 +460,7 @@ void VideoContext::SaveTimecodes(agi::fs::path const& filename) {
 void VideoContext::CloseTimecodes() {
 	ovr_fps = agi::vfr::Framerate();
 	timecodes_filename.clear();
-    OnSubtitlesCommit(0, std::set<const AssEntry*>());
+    OnSubtitlesCommit(0, std::set<const AssDialogue*>());
 	TimecodesOpen(video_fps);
 }
 
diff --git a/aegisub/src/video_context.h b/aegisub/src/video_context.h
index ea2c72e45..3c1a8cb07 100644
--- a/aegisub/src/video_context.h
+++ b/aegisub/src/video_context.h
@@ -45,11 +45,11 @@
 
 #include <wx/timer.h>
 
-class AssEntry;
-struct SubtitlesProviderErrorEvent;
+class AssDialogue;
 class ThreadedFrameSource;
-struct VideoFrame;
 class VideoProvider;
+struct SubtitlesProviderErrorEvent;
+struct VideoFrame;
 struct VideoProviderErrorEvent;
 
 namespace agi {
@@ -152,7 +152,7 @@ class VideoContext : public wxEvtHandler {
 	void OnVideoError(VideoProviderErrorEvent const& err);
 	void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
 
-	void OnSubtitlesCommit(int type, std::set<const AssEntry *> const& changed);
+	void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
 	void OnSubtitlesSave();
 
 	/// Close the video, keyframes and timecodes
diff --git a/aegisub/src/visual_tool_drag.cpp b/aegisub/src/visual_tool_drag.cpp
index 3dd7b49c4..9997605cd 100644
--- a/aegisub/src/visual_tool_drag.cpp
+++ b/aegisub/src/visual_tool_drag.cpp
@@ -31,7 +31,6 @@
 #include "video_context.h"
 #include "video_display.h"
 
-#include <libaegisub/of_type_adaptor.h>
 #include <libaegisub/util.h>
 
 #include <algorithm>
@@ -115,9 +114,9 @@ void VisualToolDrag::OnFileChanged() {
 	primary = nullptr;
 	active_feature = nullptr;
 
-	for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-		if (IsDisplayed(diag))
-			MakeFeatures(diag);
+	for (auto& diag : c->ass->Events) {
+		if (IsDisplayed(&diag))
+			MakeFeatures(&diag);
 	}
 
 	UpdateToggleButtons();
@@ -130,18 +129,18 @@ void VisualToolDrag::OnFrameChanged() {
 	auto feat = features.begin();
 	auto end = features.end();
 
-	for (auto diag : c->ass->Events | agi::of_type<AssDialogue>()) {
-		if (IsDisplayed(diag)) {
+	for (auto& diag : c->ass->Events) {
+		if (IsDisplayed(&diag)) {
 			// Features don't exist and should
-			if (feat == end || feat->line != diag)
-				MakeFeatures(diag, feat);
+			if (feat == end || feat->line != &diag)
+				MakeFeatures(&diag, feat);
 			// Move past already existing features for the line
 			else
-				while (feat != end && feat->line == diag) ++feat;
+				while (feat != end && feat->line == &diag) ++feat;
 		}
 		else {
 			// Remove all features for this line (if any)
-			while (feat != end && feat->line == diag) {
+			while (feat != end && feat->line == &diag) {
 				if (&*feat == active_feature) active_feature = nullptr;
 				feat->line = nullptr;
 				RemoveSelection(&*feat);
-- 
GitLab