From 3ee79191612e9226bb4d30af1f428b81a197e6ae Mon Sep 17 00:00:00 2001
From: Sam Yates <yates@cscs.ch>
Date: Fri, 6 Jul 2018 16:39:35 +0200
Subject: [PATCH] Migrate source/build to c++14                                
                                                                              
      (#522)

* Update `CMakeLists.txt` for C++14 option.
* Update to gcc 6 minimum.
* Update travis CI from gcc-5 to gcc-6
* Use `std::..._t` style type traits, replacing `util::` aliases.
* Use `std::cbegin`, `std::cend`, and `std::make_unique`, replacing `util::` versions.
* Remove `DEDUCED_RETURN_TYPE` macros.
* Remove redundant return type specifications.
* Use correct ADL for `begin` and `end` in (almost all) the range utilities.
* Remove redundant `mechinfo` ctor (aggregate initialization suffices).
* Use lambda capture initializers where appropriate.
* Use generic `std::equal_to`.
* Use variable templates for `math::infinity` and `math::pi`.
* Remove `enum_hash` workaround.
* Use `""s` string literals where we were using our own `""_s` construction.
* Use generic lambda for recursive lambda instead of `std::function` wrapper.
* Use generic lambda for generic arithmetic tests.

Fixes #358.
---
 .travis.yml                                   |   6 +-
 CMakeLists.txt                                |   6 +-
 arbor/algorithms.hpp                          |  46 +++-----
 arbor/backends/builtin_mech_proto.hpp         |   6 +-
 arbor/backends/gpu/shared_state.hpp           |   3 +-
 arbor/backends/multicore/shared_state.hpp     |   3 +-
 arbor/fvm_layout.cpp                          |  13 ++-
 arbor/fvm_layout.hpp                          |  14 +--
 arbor/fvm_lowered_cell_impl.hpp               |   5 +-
 arbor/math.hpp                                |  35 ++----
 arbor/mechcat.cpp                             |  14 +--
 arbor/memory/array.hpp                        |  14 +--
 arbor/memory/array_view.hpp                   |  16 +--
 arbor/memory/copy.hpp                         |   8 +-
 arbor/memory/device_coordinator.hpp           |   2 +-
 arbor/memory/fill.hpp                         |   2 +-
 arbor/memory/host_coordinator.hpp             |   2 +-
 arbor/memory/wrappers.hpp                     |  14 +--
 arbor/partition_load_balance.cpp              |   3 +-
 arbor/sampler_map.hpp                         |   7 +-
 arbor/threading/cthread_impl.hpp              |   4 +-
 arbor/threading/serial.hpp                    |   2 +-
 arbor/util/counter.hpp                        |   2 +-
 arbor/util/cycle.hpp                          |  56 +++++----
 arbor/util/deduce_return.hpp                  |  10 --
 arbor/util/either.hpp                         |  10 +-
 arbor/util/filter.hpp                         |  88 +++++---------
 arbor/util/index_into.hpp                     |  37 ++----
 arbor/util/indirect.hpp                       |  17 +--
 arbor/util/iterutil.hpp                       |  22 ++--
 arbor/util/maputil.hpp                        |  65 +++++------
 arbor/util/meta.hpp                           | 108 ++++++++----------
 arbor/util/partition.hpp                      |   2 +-
 arbor/util/partition_iterator.hpp             |   4 +-
 arbor/util/range.hpp                          |  53 ++++-----
 arbor/util/rangeutil.hpp                      |  70 +++++++-----
 arbor/util/scope_exit.hpp                     |   8 +-
 arbor/util/sentinel.hpp                       |   8 +-
 arbor/util/span.hpp                           |  19 +--
 arbor/util/transform.hpp                      |  57 +++++----
 aux/tinyopt.hpp                               |   2 +-
 example/brunel/brunel_miniapp.cpp             |   3 +-
 example/brunel/io.cpp                         |   3 +-
 example/miniapp/io.cpp                        |   5 +-
 example/miniapp/miniapp.cpp                   |   2 +-
 include/arbor/common_types.hpp                |   4 +-
 include/arbor/constants.hpp                   |   2 -
 include/arbor/generic_event.hpp               |  10 +-
 include/arbor/mc_segment.hpp                  |   7 +-
 include/arbor/mechinfo.hpp                    |  16 +--
 include/arbor/simd/avx512.hpp                 |  16 +--
 include/arbor/simd/implbase.hpp               |   1 +
 include/arbor/simd/simd.hpp                   |  24 ++--
 include/arbor/simple_sampler.hpp              |   2 +-
 include/arbor/simulation.hpp                  |   4 +-
 include/arbor/time_sequence.hpp               |   5 +-
 include/arbor/util/any.hpp                    |  11 +-
 include/arbor/util/any_ptr.hpp                |   2 +-
 include/arbor/util/enumhash.hpp               |  20 ----
 include/arbor/util/make_unique.hpp            |  18 ---
 include/arbor/util/optional.hpp               |  10 +-
 include/arbor/util/uninitialized.hpp          |  10 +-
 include/arbor/util/unique_any.hpp             |   6 +-
 lmorpho/lsys_models.cpp                       |   4 +-
 lmorpho/lsystem.cpp                           |   1 -
 modcc/functionexpander.cpp                    |   3 +-
 modcc/modccutil.hpp                           |   5 -
 modcc/module.cpp                              |   7 +-
 modcc/printer/infoprinter.cpp                 |   4 +-
 modcc/token.hpp                               |  10 --
 .../unit-distributed/distributed_listener.hpp |   3 +
 test/unit-modcc/test_printers.cpp             |  18 ++-
 test/unit/common.hpp                          |   9 --
 test/unit/test_algorithms.cpp                 |   2 +-
 test/unit/test_any.cpp                        |   9 +-
 test/unit/test_compartments.cpp               |   2 +-
 test/unit/test_cycle.cpp                      |   4 +-
 test/unit/test_fvm_layout.cpp                 |  13 ++-
 test/unit/test_fvm_lowered.cpp                |  26 ++---
 test/unit/test_maputil.cpp                    |   7 +-
 test/unit/test_math.cpp                       |  26 ++---
 test/unit/test_mc_cell.cpp                    |  11 +-
 test/unit/test_mechcat.cpp                    |  10 +-
 test/unit/test_multi_event_stream.cpp         |  11 +-
 test/unit/test_optional.cpp                   |  12 +-
 test/unit/test_range.cpp                      |  44 +++----
 test/unit/test_segment.cpp                    |   5 +-
 test/unit/test_simd.cpp                       |  17 +--
 test/unit/test_strprintf.cpp                  |   4 +-
 test/unit/test_unique_any.cpp                 |   1 -
 test/unit/test_vector.cpp                     |   6 +-
 test/unit/test_vector.cu                      |   6 +-
 test/validation/trace_analysis.hpp            |  29 ++---
 test/validation/validate_kinetic.cpp          |   3 +-
 test/validation/validate_soma.cpp             |   2 +-
 95 files changed, 584 insertions(+), 774 deletions(-)
 delete mode 100644 arbor/util/deduce_return.hpp
 delete mode 100644 include/arbor/util/enumhash.hpp
 delete mode 100644 include/arbor/util/make_unique.hpp

diff --git a/.travis.yml b/.travis.yml
index 3a6abcce..dfb339db 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@ addons:
         sources:
             - ubuntu-toolchain-r-test
         packages:
-            - g++-5
+            - g++-6
             - openmpi-bin
             - libopenmpi-dev
 
@@ -23,8 +23,8 @@ env:
     - BUILD_NAME=mpitbb   WITH_THREAD=tbb      WITH_DISTRIBUTED=mpi
 
 before_install:
-    - CC=gcc-5
-    - CXX=g++-5
+    - CC=gcc-6
+    - CXX=g++-6
 
 script: source ./scripts/travis/build.sh
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7ba6d75d..7307476e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
-# 3.9 requirement for CUDA language support.
-cmake_minimum_required(VERSION 3.9)
+# 3.8 requirement for CUDA language support.
+cmake_minimum_required(VERSION 3.8)
 
 project(arbor VERSION 0.1)
 enable_language(CXX)
@@ -103,7 +103,7 @@ include("CompilerOptions")
 add_compile_options(
     "$<$<COMPILE_LANGUAGE:CXX>:${CXXOPT_DEBUG}>"
     "$<$<COMPILE_LANGUAGE:CXX>:${CXXOPT_WALL}>")
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 14)
 
 #----------------------------------------------------------
 # Set up flags and dependencies:
diff --git a/arbor/algorithms.hpp b/arbor/algorithms.hpp
index 7ae4a5c5..4e295834 100644
--- a/arbor/algorithms.hpp
+++ b/arbor/algorithms.hpp
@@ -31,7 +31,7 @@ typename util::sequence_traits<C>::value_type
 sum(C const& c)
 {
     using value_type = typename util::sequence_traits<C>::value_type;
-    return std::accumulate(util::cbegin(c), util::cend(c), value_type{0});
+    return std::accumulate(std::cbegin(c), std::cend(c), value_type{0});
 }
 
 template <typename C>
@@ -120,30 +120,14 @@ bool is_minimal_degree(C const& c)
     return it==c.end();
 }
 
-struct generic_is_positive {
-    template <typename V>
-    bool operator()(V v) const {
-        static V zero = V{};
-        return v>zero;
-    }
-};
-
-struct generic_is_negative {
-    template <typename V>
-    bool operator()(V v) const {
-        static V zero = V{};
-        return v<zero;
-    }
-};
-
 template <typename C>
 bool all_positive(const C& c) {
-    return util::all_of(c, generic_is_positive{});
+    return util::all_of(c, [](auto v) { return v>decltype(v){}; });
 }
 
 template <typename C>
 bool all_negative(const C& c) {
-    return util::all_of(c, generic_is_negative{});
+    return util::all_of(c, [](auto v) { return v<decltype(v){}; });
 }
 
 template<typename C>
@@ -311,7 +295,10 @@ std::vector<typename C::value_type> tree_reduce(
 
 template <typename Seq, typename = util::enable_if_sequence_t<Seq&>>
 bool is_unique(const Seq& seq) {
-    return std::adjacent_find(std::begin(seq), std::end(seq)) == std::end(seq);
+    using std::begin;
+    using std::end;
+
+    return std::adjacent_find(begin(seq), end(seq)) == end(seq);
 }
 
 
@@ -319,7 +306,6 @@ bool is_unique(const Seq& seq) {
 /// about where a match was found.
 
 // TODO: consolidate these with rangeutil routines; make them sentinel
-// friendly; use ADL for begin/end (simpler with C++14).
 
 template <typename It, typename T>
 It binary_find(It b, It e, const T& value) {
@@ -328,17 +314,19 @@ It binary_find(It b, It e, const T& value) {
 }
 
 template <typename Seq, typename T>
-auto binary_find(const Seq& seq, const T& value)
-    -> decltype(binary_find(std::begin(seq), std::end(seq), value))
-{
-    return binary_find(std::begin(seq), std::end(seq), value);
+auto binary_find(const Seq& seq, const T& value) {
+    using std::begin;
+    using std::end;
+
+    return binary_find(begin(seq), end(seq), value);
 }
 
 template <typename Seq, typename T>
-auto binary_find(Seq& seq, const T& value)
-    -> decltype(binary_find(std::begin(seq), std::end(seq), value))
-{
-    return binary_find(std::begin(seq), std::end(seq), value);
+auto binary_find(Seq& seq, const T& value) {
+    using std::begin;
+    using std::end;
+
+    return binary_find(begin(seq), end(seq), value);
 }
 
 } // namespace algorithms
diff --git a/arbor/backends/builtin_mech_proto.hpp b/arbor/backends/builtin_mech_proto.hpp
index 1fcb292c..892f122f 100644
--- a/arbor/backends/builtin_mech_proto.hpp
+++ b/arbor/backends/builtin_mech_proto.hpp
@@ -14,9 +14,9 @@ inline const mechanism_info& builtin_stimulus_info() {
         {},
         // parameters
         {
-            {"delay",     spec(spec::parameter, "ms", 0, 0)},
-            {"duration",  spec(spec::parameter, "ms", 0, 0)},
-            {"amplitude", spec(spec::parameter, "nA", 0, 0)}
+            {"delay",     {spec::parameter, "ms", 0, 0}},
+            {"duration",  {spec::parameter, "ms", 0, 0}},
+            {"amplitude", {spec::parameter, "nA", 0, 0}}
         },
         // state
         {},
diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp
index 6193fc39..da1ae4ef 100644
--- a/arbor/backends/gpu/shared_state.hpp
+++ b/arbor/backends/gpu/shared_state.hpp
@@ -7,7 +7,6 @@
 
 #include <arbor/fvm_types.hpp>
 #include <arbor/ion.hpp>
-#include <arbor/util/enumhash.hpp>
 
 #include "backends/gpu/gpu_store_types.hpp"
 
@@ -77,7 +76,7 @@ struct shared_state {
     array  voltage;           // Maps CV index to membrane voltage [mV].
     array  current_density;   // Maps CV index to current density [A/m²].
 
-    std::unordered_map<ionKind, ion_state, util::enum_hash> ion_data;
+    std::unordered_map<ionKind, ion_state> ion_data;
 
     deliverable_event_stream deliverable_events;
 
diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp
index af2b19be..294f4534 100644
--- a/arbor/backends/multicore/shared_state.hpp
+++ b/arbor/backends/multicore/shared_state.hpp
@@ -12,7 +12,6 @@
 #include <arbor/fvm_types.hpp>
 #include <arbor/ion.hpp>
 #include <arbor/simd/simd.hpp>
-#include <arbor/util/enumhash.hpp>
 
 #include "backends/event.hpp"
 #include "event_queue.hpp"
@@ -96,7 +95,7 @@ struct shared_state {
     array  voltage;           // Maps CV index to membrane voltage [mV].
     array  current_density;   // Maps CV index to current density [A/m²].
 
-    std::unordered_map<ionKind, ion_state, util::enum_hash> ion_data;
+    std::unordered_map<ionKind, ion_state> ion_data;
 
     deliverable_event_stream deliverable_events;
 
diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp
index 6f3d1ee9..0f75ffe2 100644
--- a/arbor/fvm_layout.cpp
+++ b/arbor/fvm_layout.cpp
@@ -5,7 +5,6 @@
 
 #include <arbor/arbexcept.hpp>
 #include <arbor/mc_cell.hpp>
-#include <arbor/util/enumhash.hpp>
 
 #include "algorithms.hpp"
 #include "fvm_compartment.hpp"
@@ -302,7 +301,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const mechanism_catalogue& catalogue
     // Temporary table for presence of ion channels, mapping ionKind to _sorted_
     // collection of segment indices.
 
-    std::unordered_map<ionKind, std::set<size_type>, util::enum_hash> ion_segments;
+    std::unordered_map<ionKind, std::set<size_type>> ion_segments;
 
     auto update_paramset_and_validate =
         [&catalogue]
@@ -441,10 +440,14 @@ fvm_mechanism_data fvm_build_mechanism_data(const mechanism_catalogue& catalogue
         std::vector<std::vector<value_type>> param_value(nparam);
         std::vector<std::vector<value_type>> param_area_contrib(nparam);
 
-        const auto& info = *entry.second.info; // TODO: C++14, use lambda capture with initializer
-        auto& ion_configs = mechdata.ions;     // TODO: C++14 ditto
+        // (gcc 6.x bug fails to deduce const in lambda capture reference initialization)
+        const auto& info = *entry.second.info;
         auto accumulate_mech_data =
-            [&param_index, &param_value, &param_area_contrib, &config, &info, &ion_configs]
+            [
+                &info,
+                &ion_configs = mechdata.ions,
+                &param_index, &param_value, &param_area_contrib, &config
+            ]
             (size_type index, index_type cv, value_type area, const mechanism_desc& desc)
         {
             for (auto& kv: desc.values()) {
diff --git a/arbor/fvm_layout.hpp b/arbor/fvm_layout.hpp
index 9d09e5ca..ceeb3008 100644
--- a/arbor/fvm_layout.hpp
+++ b/arbor/fvm_layout.hpp
@@ -5,10 +5,8 @@
 #include <arbor/mechanism.hpp>
 #include <arbor/mechinfo.hpp>
 #include <arbor/mechcat.hpp>
-#include <arbor/util/enumhash.hpp>
 
 #include "fvm_compartment.hpp"
-#include "util/deduce_return.hpp"
 #include "util/span.hpp"
 
 namespace arb {
@@ -71,11 +69,13 @@ struct fvm_discretization {
     std::vector<size_type> cell_segment_bounds; // Partitions segment indices by cell.
     std::vector<index_type> cell_cv_bounds;      // Partitions CV indices by cell.
 
-    auto cell_segment_part() const
-        DEDUCED_RETURN_TYPE(util::partition_view(cell_segment_bounds))
+    auto cell_segment_part() const {
+        return util::partition_view(cell_segment_bounds);
+    }
 
-    auto cell_cv_part() const
-        DEDUCED_RETURN_TYPE(util::partition_view(cell_cv_bounds))
+    auto cell_cv_part() const {
+        return util::partition_view(cell_cv_bounds);
+    }
 
     size_type segment_location_cv(size_type cell_index, segment_location segloc) const {
         auto cell_segs = cell_segment_part()[cell_index];
@@ -130,7 +130,7 @@ struct fvm_mechanism_data {
     std::unordered_map<std::string, fvm_mechanism_config> mechanisms;
 
     // Ion config, indexed by ionKind.
-    std::unordered_map<ionKind, fvm_ion_config, util::enum_hash> ions;
+    std::unordered_map<ionKind, fvm_ion_config> ions;
 
     // Total number of targets (point-mechanism points)
     std::size_t ntarget = 0;
diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp
index b07fcfea..82ecbba6 100644
--- a/arbor/fvm_lowered_cell_impl.hpp
+++ b/arbor/fvm_lowered_cell_impl.hpp
@@ -9,9 +9,10 @@
 
 #include <cmath>
 #include <iterator>
+#include <memory>
+#include <stdexcept>
 #include <utility>
 #include <vector>
-#include <stdexcept>
 
 #include <arbor/assert.hpp>
 #include <arbor/common_types.hpp>
@@ -351,7 +352,7 @@ void fvm_lowered_cell_impl<B>::initialize(
         util::transform_view(keys(mech_data.mechanisms),
             [&](const std::string& name) { return mech_instance(name)->data_alignment(); }));
 
-    state_ = util::make_unique<shared_state>(ncell, D.cv_to_cell, data_alignment? data_alignment: 1u);
+    state_ = std::make_unique<shared_state>(ncell, D.cv_to_cell, data_alignment? data_alignment: 1u);
 
     // Instantiate mechanisms and ions.
 
diff --git a/arbor/math.hpp b/arbor/math.hpp
index 4db898af..6f538cf0 100644
--- a/arbor/math.hpp
+++ b/arbor/math.hpp
@@ -8,26 +8,11 @@
 namespace arb {
 namespace math {
 
-// TODO: C++14 variable template
 template <typename T>
-T constexpr pi() {
-    return T(3.1415926535897932384626433832795l);
-}
+T constexpr pi = 3.1415926535897932384626433832795l;
 
 template <typename T = float>
-T constexpr infinity() {
-    return std::numeric_limits<T>::infinity();
-}
-
-template <typename T>
-T constexpr mean(T a, T b) {
-    return (a+b) / T(2);
-}
-
-template <typename T>
-T constexpr mean(std::pair<T,T> const& p) {
-    return (p.first+p.second) / T(2);
-}
+T constexpr infinity = std::numeric_limits<T>::infinity();
 
 template <typename T>
 T constexpr square(T a) {
@@ -42,32 +27,32 @@ T constexpr cube(T a) {
 // Area of circle radius r.
 template <typename T>
 T constexpr area_circle(T r) {
-    return pi<T>() * square(r);
+    return pi<T> * square(r);
 }
 
 // Surface area of conic frustrum excluding the discs at each end,
 // with length L, end radii r1, r2.
 template <typename T>
 T constexpr area_frustrum(T L, T r1, T r2) {
-    return pi<T>() * (r1+r2) * sqrt(square(L) + square(r1-r2));
+    return pi<T> * (r1+r2) * sqrt(square(L) + square(r1-r2));
 }
 
 // Volume of conic frustrum of length L, end radii r1, r2.
 template <typename T>
 T constexpr volume_frustrum(T L, T r1, T r2) {
-    return pi<T>()/T(3) * (square(r1+r2) - r1*r2) * L;
+    return pi<T>/T(3) * (square(r1+r2) - r1*r2) * L;
 }
 
 // Volume of a sphere radius r.
 template <typename T>
 T constexpr volume_sphere(T r) {
-    return T(4)/T(3) * pi<T>() * cube(r);
+    return T(4)/T(3) * pi<T> * cube(r);
 }
 
 // Surface area of a sphere radius r.
 template <typename T>
 T constexpr area_sphere(T r) {
-    return T(4) * pi<T>() * square(r);
+    return T(4) * pi<T> * square(r);
 }
 
 // Linear interpolation by u in interval [a,b]: (1-u)*a + u*b.
@@ -87,7 +72,7 @@ int signum(T x) {
 // next_pow2(x) returns 0 if x==0, else returns smallest 2^k such
 // that 2^k>=x.
 
-template <typename U, typename = typename std::enable_if<std::is_unsigned<U>::value>::type>
+template <typename U, typename = std::enable_if_t<std::is_unsigned<U>::value>>
 U next_pow2(U x) {
     --x;
     for (unsigned s=1; s<std::numeric_limits<U>::digits; s<<=1) {
@@ -121,8 +106,8 @@ namespace impl {
 template <
     typename T,
     typename U,
-    typename C = typename std::common_type<T, U>::type,
-    typename Signed = typename std::is_signed<C>::type
+    typename C = std::common_type_t<T, U>,
+    typename Signed = std::is_signed<C>
 >
 C round_up(T v, U b) {
     C m = v%b;
diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp
index ed314a78..26d21b93 100644
--- a/arbor/mechcat.cpp
+++ b/arbor/mechcat.cpp
@@ -5,14 +5,13 @@
 
 #include <arbor/arbexcept.hpp>
 #include <arbor/mechcat.hpp>
-#include <arbor/util/make_unique.hpp>
 
 #include "util/maputil.hpp"
 
 namespace arb {
 
 using util::value_by_key;
-using util::make_unique;
+using std::make_unique;
 
 void mechanism_catalogue::add(const std::string& name, mechanism_info info) {
     if (has(name)) {
@@ -133,20 +132,17 @@ std::unique_ptr<mechanism> mechanism_catalogue::instance_impl(std::type_index ti
     }
 
     std::unique_ptr<mechanism> mech = prototype->clone();
-    // TODO: make recursive lambda without std::function in C++14
-    std::function<void (std::string, mechanism*)> apply_globals =
-        [this, &apply_globals](const std::string& name, mechanism* mptr)
-    {
+
+    auto apply_globals = [this](auto& self, const std::string& name, mechanism* mptr) -> void {
         if (auto p = value_by_key(derived_map_, name)) {
-            apply_globals(p->parent, mptr);
+            self(self, p->parent, mptr);
 
             for (auto& kv: p->globals) {
                 mptr->set_global(kv.first, kv.second);
             }
         }
     };
-
-    apply_globals(name, mech.get());
+    apply_globals(apply_globals, name, mech.get());
     return mech;
 }
 
diff --git a/arbor/memory/array.hpp b/arbor/memory/array.hpp
index e2915e6f..59fd4c39 100644
--- a/arbor/memory/array.hpp
+++ b/arbor/memory/array.hpp
@@ -68,8 +68,8 @@ namespace impl {
     template <typename T>
     struct is_array :
         std::conditional<
-            impl::is_array_by_value<typename std::decay<T>::type>::value ||
-            impl::is_array_view    <typename std::decay<T>::type>::value,
+            impl::is_array_by_value<std::decay_t<T>>::value ||
+            impl::is_array_view    <std::decay_t<T>>::value,
             std::true_type, std::false_type
         >::type
     {};
@@ -116,7 +116,7 @@ public:
     // constructor by size
     template <
         typename I,
-        typename = typename std::enable_if<std::is_integral<I>::value>::type>
+        typename = std::enable_if_t<std::is_integral<I>::value>>
     array(I n) :
         base(coordinator_type().allocate(n))
     {
@@ -129,8 +129,8 @@ public:
     // constructor by size with default value
     template <
         typename II, typename TT,
-        typename = typename std::enable_if<std::is_integral<II>::value>::type,
-        typename = typename std::enable_if<std::is_convertible<TT,value_type>::value>::type >
+        typename = std::enable_if_t<std::is_integral<II>::value>,
+        typename = std::enable_if_t<std::is_convertible<TT,value_type>::value> >
     array(II n, TT value) :
         base(coordinator_type().allocate(n))
     {
@@ -168,7 +168,7 @@ public:
     // copy constructor where other is an array, array_view or array_reference
     template <
         typename Other,
-        typename = typename std::enable_if<impl::is_array_t<Other>::value>::type
+        typename = std::enable_if_t<impl::is_array_t<Other>::value>
     >
     array(const Other& other) :
         base(coordinator_type().allocate(other.size()))
@@ -216,7 +216,7 @@ public:
 
     template <
         typename It,
-        typename = arb::util::enable_if_t<arb::util::is_random_access_iterator<It>::value> >
+        typename = std::enable_if_t<arb::util::is_random_access_iterator<It>::value> >
     array(It b, It e) :
         base(coordinator_type().allocate(std::distance(b, e)))
     {
diff --git a/arbor/memory/array_view.hpp b/arbor/memory/array_view.hpp
index 3c187761..f63ccb29 100644
--- a/arbor/memory/array_view.hpp
+++ b/arbor/memory/array_view.hpp
@@ -138,7 +138,7 @@ public:
     // Constructors
     template <
         typename Other,
-        typename = typename std::enable_if< impl::is_array<Other>::value >::type
+        typename = std::enable_if_t< impl::is_array<Other>::value >
     >
     explicit array_view(Other&& other) :
         pointer_(other.data()), size_(other.size())
@@ -146,7 +146,7 @@ public:
         #ifdef VERBOSE
         std::cout << util::green("array_view(&&Other) ")
                   << "\n  this  " << util::pretty_printer<array_view>::print(*this)
-                  << "\n  other " << util::pretty_printer<typename std::decay<Other>::type>::print(other)
+                  << "\n  other " << util::pretty_printer<std::decay_t<Other>>::print(other)
                   << "\n";
         #endif
     }
@@ -210,7 +210,7 @@ public:
 
     template <
         typename Other,
-        typename = typename std::enable_if< impl::is_array<Other>::value >::type
+        typename = std::enable_if_t< impl::is_array<Other>::value >
     >
     array_view operator=(Other&& other) {
         #if VERBOSE
@@ -286,7 +286,7 @@ public:
     }
 
     static constexpr auto
-    alignment() -> decltype(coordinator_type::alignment()) {
+    alignment() {
         return coordinator_type::alignment();
     }
 
@@ -342,7 +342,7 @@ public:
     // Constructors
     template <
         typename Other,
-        typename = typename std::enable_if< impl::is_array<Other>::value >::type
+        typename = std::enable_if_t< impl::is_array<Other>::value >
     >
     const_array_view(const Other& other) :
         pointer_(other.data()), size_(other.size())
@@ -350,7 +350,7 @@ public:
 #if VERBOSE
         std::cout << util::green("const_array_view(const Other&)")
                   << "\n  this  " << util::pretty_printer<const_array_view>::print(*this)
-                  << "\n  other " << util::pretty_printer<typename std::decay<Other>::type>::print(other)
+                  << "\n  other " << util::pretty_printer<std::decay_t<Other>>::print(other)
                   << std::endl;
 #endif
     }
@@ -401,7 +401,7 @@ public:
 
     template <
         typename Other,
-        typename = typename std::enable_if< impl::is_array<Other>::value >::type
+        typename = std::enable_if_t< impl::is_array<Other>::value >
     >
     const_array_view operator=(Other&& other) {
 #if VERBOSE
@@ -458,7 +458,7 @@ public:
     }
 
     static constexpr auto
-    alignment() -> decltype(coordinator_type::alignment()) {
+    alignment() {
         return coordinator_type::alignment();
     }
 
diff --git a/arbor/memory/copy.hpp b/arbor/memory/copy.hpp
index 9d864376..87f7495b 100644
--- a/arbor/memory/copy.hpp
+++ b/arbor/memory/copy.hpp
@@ -17,9 +17,9 @@ void copy(LHS&& from, RHS&& to) {
 #ifdef VERBOSE
     std::cerr
         << util::blue("copy") << " "
-        << util::pretty_printer<typename std::decay<LHS>::type>::print(from)
+        << util::pretty_printer<std::decay_t<LHS>>::print(from)
         << util::cyan(" -> ")
-        << util::pretty_printer<typename std::decay<RHS>::type>::print(to)  << "\n";
+        << util::pretty_printer<std::decay_t<RHS>>::print(to)  << "\n";
 #endif
     // adapt views to the inputs
     auto lhs = make_const_view(from);
@@ -33,7 +33,7 @@ void copy(LHS&& from, RHS&& to) {
 
 template <typename LHS, typename T>
 void fill(LHS&& target, T value) {
-    using lhs_type = typename std::decay<LHS>::type;
+    using lhs_type = std::decay_t<LHS>;
     static_assert(
         std::is_convertible<T, typename lhs_type::value_type>::value,
         "can't fill container with a value of the supplied type"
@@ -41,7 +41,7 @@ void fill(LHS&& target, T value) {
 #ifdef VERBOSE
     std::cerr
         << util::blue("fill") << " "
-        << util::pretty_printer<typename std::decay<LHS>::type>::print(target)
+        << util::pretty_printer<std::decay_t<LHS>>::print(target)
         << util::cyan(" <- ")
         << T(value) << "\n";
 #endif
diff --git a/arbor/memory/device_coordinator.hpp b/arbor/memory/device_coordinator.hpp
index 812684f4..d886a0be 100644
--- a/arbor/memory/device_coordinator.hpp
+++ b/arbor/memory/device_coordinator.hpp
@@ -236,7 +236,7 @@ public:
     }
 
     static constexpr
-    auto alignment() -> decltype(Allocator_::alignment()) {
+    auto alignment() {
         return Allocator_::alignment();
     }
 
diff --git a/arbor/memory/fill.hpp b/arbor/memory/fill.hpp
index c118c139..11d0da59 100644
--- a/arbor/memory/fill.hpp
+++ b/arbor/memory/fill.hpp
@@ -33,7 +33,7 @@ void fill64(uint64_t* v, uint64_t value, std::size_t n);
 
 #define FILL(N) \
 template <typename T> \
-typename std::enable_if<sizeof(T)==sizeof(uint ## N ## _t)>::type \
+std::enable_if_t<sizeof(T)==sizeof(uint ## N ## _t)> \
 fill(T* ptr, T value, std::size_t n) { \
     using I = uint ## N ## _t; \
     I v; \
diff --git a/arbor/memory/host_coordinator.hpp b/arbor/memory/host_coordinator.hpp
index 19f770df..0f814cdb 100644
--- a/arbor/memory/host_coordinator.hpp
+++ b/arbor/memory/host_coordinator.hpp
@@ -177,7 +177,7 @@ public:
     }
 
     static constexpr auto
-    alignment() -> decltype(Allocator::alignment()) {
+    alignment() {
         return Allocator::alignment();
     }
 
diff --git a/arbor/memory/wrappers.hpp b/arbor/memory/wrappers.hpp
index f51daa03..6f3d6532 100644
--- a/arbor/memory/wrappers.hpp
+++ b/arbor/memory/wrappers.hpp
@@ -87,7 +87,7 @@ namespace util {
 
     template <typename T>
     constexpr bool is_on_host_v() {
-        return is_on_host<typename std::decay<T>::type>::value;
+        return is_on_host<std::decay_t<T>>::value;
     }
 
     template <typename T>
@@ -104,7 +104,7 @@ namespace util {
 
     template <typename T>
     constexpr bool is_on_gpu_v() {
-        return is_on_gpu<typename std::decay<T>::type>::value;
+        return is_on_gpu<std::decay_t<T>>::value;
     }
 }
 
@@ -118,15 +118,15 @@ namespace util {
 // host
 template <
     typename C,
-    typename = typename std::enable_if<util::is_on_host_v<C>()>::type
+    typename = std::enable_if_t<util::is_on_host_v<C>()>
 >
-auto on_host(const C& c) -> decltype(make_const_view(c)) {
+auto on_host(const C& c) {
     return make_const_view(c);
 }
 
 template <
     typename C,
-    typename = typename std::enable_if<util::is_on_gpu_v<C>()>::type
+    typename = std::enable_if_t<util::is_on_gpu_v<C>()>
 >
 auto on_host(const C& c) -> host_vector<typename C::value_type> {
     using T = typename C::value_type;
@@ -136,7 +136,7 @@ auto on_host(const C& c) -> host_vector<typename C::value_type> {
 // gpu
 template <
     typename C,
-    typename = typename std::enable_if<util::is_on_gpu_v<C>()>::type
+    typename = std::enable_if_t<util::is_on_gpu_v<C>()>
 >
 auto on_gpu(const C& c) -> decltype(make_const_view(c)) {
     return make_const_view(c);
@@ -144,7 +144,7 @@ auto on_gpu(const C& c) -> decltype(make_const_view(c)) {
 
 template <
     typename C,
-    typename = typename std::enable_if<util::is_on_host_v<C>()>::type
+    typename = std::enable_if_t<util::is_on_host_v<C>()>
 >
 auto on_gpu(const C& c) -> device_vector<typename C::value_type> {
     using T = typename C::value_type;
diff --git a/arbor/partition_load_balance.cpp b/arbor/partition_load_balance.cpp
index c9a18ec2..472c61ef 100644
--- a/arbor/partition_load_balance.cpp
+++ b/arbor/partition_load_balance.cpp
@@ -1,5 +1,4 @@
 #include <arbor/distributed_context.hpp>
-#include <arbor/util/enumhash.hpp>
 #include <arbor/domain_decomposition.hpp>
 #include <arbor/recipe.hpp>
 
@@ -46,7 +45,7 @@ domain_decomposition partition_load_balance(const recipe& rec,
 
     // Local load balance
 
-    std::unordered_map<cell_kind, std::vector<cell_gid_type>, arb::util::enum_hash>
+    std::unordered_map<cell_kind, std::vector<cell_gid_type>>
         kind_lists;
     for (auto gid: make_span(gid_part[domain_id])) {
         kind_lists[rec.get_cell_kind(gid)].push_back(gid);
diff --git a/arbor/sampler_map.hpp b/arbor/sampler_map.hpp
index 5d137fbf..e99d6b4a 100644
--- a/arbor/sampler_map.hpp
+++ b/arbor/sampler_map.hpp
@@ -13,7 +13,6 @@
 #include <arbor/sampling.hpp>
 #include <arbor/schedule.hpp>
 
-#include "util/deduce_return.hpp"
 #include "util/transform.hpp"
 
 namespace arb {
@@ -52,13 +51,13 @@ private:
     std::mutex m_;
 
     static sampler_association& second(assoc_map::value_type& p) { return p.second; }
-    auto assoc_view() DEDUCED_RETURN_TYPE((util::transform_view(map_, &sampler_association_map::second)))
+    auto assoc_view() { return util::transform_view(map_, &sampler_association_map::second); }
 
 public:
     // Range-like view presents just the associations, omitting the handles.
 
-    auto begin() DEDUCED_RETURN_TYPE(assoc_view().begin());
-    auto end()   DEDUCED_RETURN_TYPE(assoc_view().end());
+    auto begin() { return assoc_view().begin(); }
+    auto end()   { return assoc_view().end(); }
 };
 
 // Manage associations between probe ids, probe tags, and (lowered cell) probe handles.
diff --git a/arbor/threading/cthread_impl.hpp b/arbor/threading/cthread_impl.hpp
index 52e84be0..8d0fe219 100644
--- a/arbor/threading/cthread_impl.hpp
+++ b/arbor/threading/cthread_impl.hpp
@@ -140,7 +140,7 @@ public :
       return data[global_task_pool.get_current_thread()];
     }
 
-    auto size() -> decltype(data.size()) const { return data.size(); }
+    auto size() const { return data.size(); }
 
     iterator begin() { return data.begin(); }
     iterator end()   { return data.end(); }
@@ -163,7 +163,7 @@ private:
 
     // call a function of type X f() in a lock
     template<typename F>
-    auto critical(F f) -> decltype(f()) {
+    decltype(auto) critical(F f) {
         impl::lock lock{mutex};
         return f();
     }
diff --git a/arbor/threading/serial.hpp b/arbor/threading/serial.hpp
index 103818f6..af3b0594 100644
--- a/arbor/threading/serial.hpp
+++ b/arbor/threading/serial.hpp
@@ -34,7 +34,7 @@ public :
     T& local() { return data[0]; }
     const T& local() const { return data[0]; }
 
-    auto size() -> decltype(data.size()) const { return data.size(); }
+    auto size() const { return data.size(); }
 
     iterator begin() { return data.begin(); }
     iterator end()   { return data.end(); }
diff --git a/arbor/util/counter.hpp b/arbor/util/counter.hpp
index 4328876d..fc59c4a4 100644
--- a/arbor/util/counter.hpp
+++ b/arbor/util/counter.hpp
@@ -9,7 +9,7 @@
 namespace arb {
 namespace util {
 
-template <typename V, typename = typename std::enable_if<std::is_integral<V>::value>::type>
+template <typename V, typename = std::enable_if_t<std::is_integral<V>::value>>
 struct counter {
     using difference_type = V;
     using value_type = V;
diff --git a/arbor/util/cycle.hpp b/arbor/util/cycle.hpp
index 83fe7e08..5664be0a 100644
--- a/arbor/util/cycle.hpp
+++ b/arbor/util/cycle.hpp
@@ -1,9 +1,11 @@
 #pragma once
 
 #include <initializer_list>
+#include <type_traits>
 #include <utility>
-#include <util/iterutil.hpp>
-#include <util/range.hpp>
+
+#include "util/iterutil.hpp"
+#include "util/range.hpp"
 
 namespace arb {
 namespace util {
@@ -183,35 +185,39 @@ cyclic_iterator<I, S> make_cyclic_iterator(const I& iter, const S& sentinel) {
 }
 
 
-template <
-    typename Seq,
-    typename SeqIter = typename sequence_traits<Seq>::const_iterator,
-    typename SeqSentinel = typename sequence_traits<Seq>::const_sentinel,
-    typename = enable_if_t<std::is_same<SeqIter, SeqSentinel>::value>
->
-range<cyclic_iterator<SeqIter, SeqSentinel> > cyclic_view(const Seq& s) {
-    return { make_cyclic_iterator(util::cbegin(s), util::cend(s)),
-             make_cyclic_iterator(util::cend(s), util::cend(s)) };
+// TODO C++17: simplify with constexpr-if
+namespace cycle_impl {
+    using std::begin;
+    using std::end;
+
+    // cycle over regular sequences:
+    template <typename Seq>
+    auto cycle_(Seq&& s, std::true_type) {
+        auto b = begin(s);
+        auto e = end(s);
+        return make_range(make_cyclic_iterator(b, e), make_cyclic_iterator(e, e));
+    }
+
+    // cycle over sentinel-terminated sequences:
+    template <typename Seq>
+    auto cycle_(Seq&& s, std::false_type) {
+        auto b = begin(s);
+        auto e = end(s);
+        return make_range(make_cyclic_iterator(b, e), e);
+    }
 }
 
-template <
-    typename Seq,
-    typename SeqIter = typename sequence_traits<Seq>::const_iterator,
-    typename SeqSentinel = typename sequence_traits<Seq>::const_sentinel,
-    typename = enable_if_t<!std::is_same<SeqIter, SeqSentinel>::value>
->
-range<cyclic_iterator<SeqIter, SeqSentinel>, SeqSentinel>
-cyclic_view(const Seq& s) {
-    return { make_cyclic_iterator(util::cbegin(s), util::cend(s)), util::cend(s) };
+template <typename Seq>
+auto cyclic_view(Seq&& s) {
+    return cycle_impl::cycle_(std::forward<Seq>(s), is_regular_sequence<Seq&&>{});
 }
 
 // Handle initializer lists
 template <typename T>
-range<cyclic_iterator<typename std::initializer_list<T>::const_iterator,
-                      typename std::initializer_list<T>::const_iterator> >
-cyclic_view(const std::initializer_list<T> &list) {
-    return { make_cyclic_iterator(util::cbegin(list), util::cend(list)),
-             make_cyclic_iterator(util::cend(list), util::cend(list)) };
+auto cyclic_view(const std::initializer_list<T>& list) {
+    return make_range(
+        make_cyclic_iterator(list.begin(), list.end()),
+        make_cyclic_iterator(list.end(), list.end()));
 }
 
 } // namespace util
diff --git a/arbor/util/deduce_return.hpp b/arbor/util/deduce_return.hpp
deleted file mode 100644
index 1211ebfb..00000000
--- a/arbor/util/deduce_return.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-/* Just one macro, to fill the gap before C++14 */
-
-#ifdef DEDUCED_RETURN_TYPE
-#undef DEDUCED_RETURN_TYPE
-#endif
-
-#define DEDUCED_RETURN_TYPE(expr) -> decltype(expr) { return expr; }
-
diff --git a/arbor/util/either.hpp b/arbor/util/either.hpp
index 8b40b735..faea135a 100644
--- a/arbor/util/either.hpp
+++ b/arbor/util/either.hpp
@@ -116,7 +116,7 @@ public:
         typename A_ = A,
         bool a_ = std::is_default_constructible<A_>::value,
         bool b_ = std::is_default_constructible<B>::value,
-        typename = enable_if_t<a_ || (!a_ && b_)>,
+        typename = std::enable_if_t<a_ || (!a_ && b_)>,
         std::size_t w_ = a_? 0: 1
     >
     either() noexcept(std::is_nothrow_default_constructible<typename getter<w_>::type>::value):
@@ -132,7 +132,7 @@ public:
 
     template <
         typename B_ = B,
-        typename = enable_if_t<!std::is_same<A, B_>::value>
+        typename = std::enable_if_t<!std::is_same<A, B_>::value>
     >
     either(const B& b) noexcept(std::is_nothrow_copy_constructible<B>::value): which(1) {
         getter<1>::field(*this).construct(b);
@@ -144,7 +144,7 @@ public:
 
     template <
         typename B_ = B,
-        typename = enable_if_t<!std::is_same<A, B_>::value>
+        typename = std::enable_if_t<!std::is_same<A, B_>::value>
     >
     either(B&& b) noexcept(std::is_nothrow_move_constructible<B>::value): which(1) {
         getter<1>::field(*this).construct(std::move(b));
@@ -309,12 +309,12 @@ public:
 
     // pointer to element access: return nullptr if it does not hold this item
     template <std::size_t I>
-    auto ptr() -> decltype(getter<I>::ptr(which, *this)) {
+    auto ptr() {
         return getter<I>::ptr(which, *this);
     }
 
     template <std::size_t I>
-    auto ptr() const -> decltype(getter<I>::ptr(which, *this)) {
+    auto ptr() const {
         return getter<I>::ptr(which, *this);
     }
 
diff --git a/arbor/util/filter.hpp b/arbor/util/filter.hpp
index c8ff4419..43f6fd88 100644
--- a/arbor/util/filter.hpp
+++ b/arbor/util/filter.hpp
@@ -69,11 +69,12 @@ class filter_iterator {
 public:
     using value_type = typename std::iterator_traits<I>::value_type;
     using difference_type = typename std::iterator_traits<I>::difference_type;
-    using iterator_category = typename std::conditional<
-        is_forward_iterator<I>::value,
-        std::forward_iterator_tag,
-        std::input_iterator_tag
-    >::type;
+    using iterator_category =
+        std::conditional_t<
+            is_forward_iterator<I>::value,
+            std::forward_iterator_tag,
+            std::input_iterator_tag
+        >;
 
     using pointer = typename std::iterator_traits<I>::pointer;
     using reference = typename std::iterator_traits<I>::reference;
@@ -129,7 +130,7 @@ public:
 
     // forward and input iterator requirements
 
-    auto operator*() const -> decltype(*(this->inner_)) {
+    decltype(auto) operator*() const {
         advance();
         return *inner_;
     }
@@ -182,62 +183,35 @@ public:
 };
 
 template <typename I, typename S, typename F>
-filter_iterator<I, S, util::decay_t<F>> make_filter_iterator(const I& i, const S& end, const F& f) {
-    return filter_iterator<I, S, util::decay_t<F>>(i, end, f);
+filter_iterator<I, S, std::decay_t<F>> make_filter_iterator(const I& i, const S& end, const F& f) {
+    return filter_iterator<I, S, std::decay_t<F>>(i, end, f);
 }
 
-// filter over const and non-const regular sequences:
-
-template <
-    typename Seq,
-    typename F,
-    typename seq_iter = typename sequence_traits<Seq>::iterator,
-    typename seq_sent = typename sequence_traits<Seq>::sentinel,
-    typename = enable_if_t<std::is_same<seq_iter, seq_sent>::value>
->
-range<filter_iterator<seq_iter, seq_iter, util::decay_t<F>>>
-filter(Seq& s, const F& f) {
-    return {make_filter_iterator(std::begin(s), std::end(s), f),
-            make_filter_iterator(std::end(s), std::end(s), f)};
-}
-
-template <
-    typename Seq,
-    typename F,
-    typename seq_citer = typename sequence_traits<Seq>::const_iterator,
-    typename seq_csent = typename sequence_traits<Seq>::const_sentinel,
-    typename = enable_if_t<std::is_same<seq_citer, seq_csent>::value>
->
-range<filter_iterator<seq_citer, seq_citer, util::decay_t<F>>>
-filter(const Seq& s, const F& f) {
-    return {make_filter_iterator(util::cbegin(s), util::cend(s), f),
-            make_filter_iterator(util::cend(s), util::cend(s), f)};
-}
+// TODO C++17: simplify with constexpr-if
+namespace filter_impl {
+    using std::begin;
+    using std::end;
+
+    // filter over regular sequences:
+    template <typename Seq, typename F>
+    auto filter_(Seq&& s, const F& f, std::true_type) {
+        auto b = begin(s);
+        auto e = end(s);
+        return make_range(make_filter_iterator(b, e, f), make_filter_iterator(e, e, f));
+    }
 
-// filter over const and non-const sentinel-terminated sequences:
-
-template <
-    typename Seq,
-    typename F,
-    typename seq_iter = typename sequence_traits<Seq>::iterator,
-    typename seq_sent = typename sequence_traits<Seq>::sentinel,
-    typename = enable_if_t<!std::is_same<seq_iter, seq_sent>::value>
->
-range<filter_iterator<seq_iter, seq_sent, util::decay_t<F>>, seq_sent>
-filter(Seq& s, const F& f) {
-    return {make_filter_iterator(std::begin(s), std::end(s), f), std::end(s)};
+    // filter over sentinel-terminated sequences:
+    template <typename Seq, typename F>
+    auto filter_(Seq&& s, const F& f, std::false_type) {
+        auto b = begin(s);
+        auto e = end(s);
+        return make_range(make_filter_iterator(b, e, f), e);
+    }
 }
 
-template <
-    typename Seq,
-    typename F,
-    typename seq_citer = typename sequence_traits<Seq>::const_iterator,
-    typename seq_csent = typename sequence_traits<Seq>::const_sentinel,
-    typename = enable_if_t<!std::is_same<seq_citer, seq_csent>::value>
->
-range<filter_iterator<seq_citer, seq_csent, util::decay_t<F>>, seq_csent>
-filter(const Seq& s, const F& f) {
-    return {make_filter_iterator(util::cbegin(s), util::cend(s), f), util::cend(s)};
+template <typename Seq, typename F>
+auto filter(Seq&& s, const F& f) {
+    return filter_impl::filter_(std::forward<Seq>(s), f, is_regular_sequence<Seq&&>{});
 }
 
 } // namespace util
diff --git a/arbor/util/index_into.hpp b/arbor/util/index_into.hpp
index 879c3bac..c78e706b 100644
--- a/arbor/util/index_into.hpp
+++ b/arbor/util/index_into.hpp
@@ -30,14 +30,14 @@ struct index_into_iterator {
     using difference_type = value_type;
     using pointer = const value_type*;
     using reference = const value_type&;
-    using iterator_category = typename
-        std::conditional<
+    using iterator_category =
+        std::conditional_t<
             std::is_same<Sup, SupEnd>::value
                 && is_bidirectional_iterator_t<Sup>::value
                 && is_bidirectional_iterator_t<Sub>::value,
             std::bidirectional_iterator_tag,
             std::forward_iterator_tag
-        >::type;
+        >;
 
     index_into_iterator(const Sub& sub, const Sub& sub_end, const Sup& sup, const SupEnd& sup_end):
         sub(sub), sub_end(sub_end), sup(sup), sup_end(sup_end), idx(0)
@@ -120,35 +120,18 @@ private:
     }
 };
 
-template <
-    typename Sub,
-    typename Super,
-    typename Canon = decltype(canonical_view(std::declval<Sub>()))
->
-auto index_into(const Sub& sub, const Super& sup)
-    -> range<
-           index_into_iterator<
-                typename sequence_traits<Canon>::const_iterator,
-                typename sequence_traits<Super>::const_iterator,
-                typename sequence_traits<Super>::const_sentinel
-           >
-       >
-{
-    using iterator =
-        index_into_iterator<
-            typename sequence_traits<Canon>::const_iterator,
-            typename sequence_traits<Super>::const_iterator,
-            typename sequence_traits<Super>::const_sentinel
-        >;
-
+template <typename Sub, typename Super>
+auto index_into(const Sub& sub, const Super& sup) {
     using std::begin;
     using std::end;
 
     auto canon = canonical_view(sub);
-    iterator b(canon.begin(), canon.end(), begin(sup), end(sup));
-    iterator e(canon.end(), canon.end(), begin(sup), end(sup));
+    using iterator = index_into_iterator<decltype(canon.begin()), decltype(begin(sup)), decltype(end(sup))>;
 
-    return range<iterator>(b, e);
+    return make_range(
+        iterator(canon.begin(), canon.end(), begin(sup), end(sup)),
+        iterator(canon.end(), canon.end(), begin(sup), end(sup))
+    );
 }
 
 } // namespace util
diff --git a/arbor/util/indirect.hpp b/arbor/util/indirect.hpp
index 0205a15a..301172b2 100644
--- a/arbor/util/indirect.hpp
+++ b/arbor/util/indirect.hpp
@@ -10,9 +10,8 @@
 
 #include <utility>
 
-#include <util/deduce_return.hpp>
-#include <util/transform.hpp>
-#include <util/meta.hpp>
+#include "util/transform.hpp"
+#include "util/meta.hpp"
 
 namespace arb {
 namespace util {
@@ -34,14 +33,16 @@ namespace impl {
 }
 
 template <typename RASeq, typename Seq>
-auto indirect_view(RASeq& data, const Seq& index_map)
-DEDUCED_RETURN_TYPE(transform_view(index_map, impl::indirect_accessor<RASeq&>(data)));
+auto indirect_view(RASeq& data, const Seq& index_map) {
+    return transform_view(index_map, impl::indirect_accessor<RASeq&>(data));
+}
 
 // icpc 17 fails to disambiguate without further qualification, so
 // we replace `template <typename RASeq, typename Seq>` with the following:
-template <typename RASeq, typename Seq, typename = util::enable_if_t<!std::is_reference<RASeq>::value>>
-auto indirect_view(RASeq&& data, const Seq& index_map)
-DEDUCED_RETURN_TYPE(transform_view(index_map, impl::indirect_accessor<RASeq>(std::move(data))));
+template <typename RASeq, typename Seq, typename = std::enable_if_t<!std::is_reference<RASeq>::value>>
+auto indirect_view(RASeq&& data, const Seq& index_map) {
+    return transform_view(index_map, impl::indirect_accessor<RASeq>(std::move(data)));
+}
 
 } // namespace util
 } // namespace arb
diff --git a/arbor/util/iterutil.hpp b/arbor/util/iterutil.hpp
index 53b68eec..90a376ee 100644
--- a/arbor/util/iterutil.hpp
+++ b/arbor/util/iterutil.hpp
@@ -23,7 +23,7 @@ namespace util {
  * second is used when we can just return std::prev(end).
  */
 template <typename I, typename E>
-enable_if_t<
+std::enable_if_t<
     is_forward_iterator<I>::value &&
         (!is_bidirectional_iterator<E>::value || !std::is_constructible<I, E>::value),
     I>
@@ -37,14 +37,14 @@ upto(I iter, E end) {
 }
 
 template <typename I, typename E>
-enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I>
+std::enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I>
 upto(I iter, E end) {
     return iter==I{end}? iter: I{std::prev(end)};
 }
 
 template <typename I, typename E,
           typename C = common_random_access_iterator_t<I,E>>
-enable_if_t<std::is_same<I, E>::value ||
+std::enable_if_t<std::is_same<I, E>::value ||
             (has_common_random_access_iterator<I,E>::value &&
              is_forward_iterator<I>::value),
             typename std::iterator_traits<C>::difference_type>
@@ -53,7 +53,7 @@ distance(I first, E last) {
 }
 
 template <typename I, typename E>
-enable_if_t<!has_common_random_access_iterator<I, E>::value &&
+std::enable_if_t<!has_common_random_access_iterator<I, E>::value &&
             is_forward_iterator<I>::value,
             typename std::iterator_traits<I>::difference_type>
 distance(I first, E last) {
@@ -70,16 +70,18 @@ distance(I first, E last) {
  * generic front() and back() methods for containers or ranges
  */
 
-// TODO: Use ADL begin and end when we avoid explicit return type in C++14
 template <typename Seq>
-auto front(Seq& seq) -> decltype(*std::begin(seq)) {
-    return *std::begin(seq);
+decltype(auto) front(Seq& seq) {
+    using std::begin;
+    return *begin(seq);
 }
 
-// TODO: Use ADL begin and end when we avoid explicit return type in C++14
 template <typename Seq>
-auto back(Seq& seq) -> decltype(*std::begin(seq)) {
-    return *upto(std::begin(seq), std::end(seq));
+decltype(auto) back(Seq& seq) {
+    using std::begin;
+    using std::end;
+
+    return *upto(begin(seq), end(seq));
 }
 
 /*
diff --git a/arbor/util/maputil.hpp b/arbor/util/maputil.hpp
index 223b400b..afb2f3ad 100644
--- a/arbor/util/maputil.hpp
+++ b/arbor/util/maputil.hpp
@@ -6,10 +6,10 @@
 #include <utility>
 #include <type_traits>
 
-#include <util/deduce_return.hpp>
-#include <util/meta.hpp>
 #include <arbor/util/optional.hpp>
-#include <util/transform.hpp>
+
+#include "util/meta.hpp"
+#include "util/transform.hpp"
 
 // Convenience views, algorithms for maps and map-like containers.
 
@@ -19,17 +19,18 @@ namespace util {
 // View over the keys (first elements) in a sequence of pairs or tuples.
 
 template <typename Seq>
-auto keys(Seq&& m) DEDUCED_RETURN_TYPE(util::transform_view(std::forward<Seq>(m), util::first))
+auto keys(Seq&& m) {
+    return util::transform_view(std::forward<Seq>(m), util::first);
+}
 
 // Is a container/sequence a map?
 
-namespace impl {
+namespace maputil_impl {
     template <
         typename C,
         typename seq_value = typename sequence_traits<C>::value_type,
-        typename K = typename std::tuple_element<0, seq_value>::type,
-        typename V = typename std::tuple_element<0, seq_value>::type,
-        typename find_value = decay_t<decltype(*std::declval<C>().find(std::declval<K>()))>
+        typename K = std::tuple_element_t<0, seq_value>,
+        typename find_value = std::decay_t<decltype(*std::declval<C>().find(std::declval<K>()))>
     >
     struct assoc_test: std::integral_constant<bool, std::is_same<seq_value, find_value>::value> {};
 }
@@ -38,7 +39,7 @@ template <typename Seq, typename = void>
 struct is_associative_container: std::false_type {};
 
 template <typename Seq>
-struct is_associative_container<Seq, void_t<impl::assoc_test<Seq>>>: impl::assoc_test<Seq> {};
+struct is_associative_container<Seq, void_t<maputil_impl::assoc_test<Seq>>>: maputil_impl::assoc_test<Seq> {};
 
 // Find value in a sequence of key-value pairs or in a key-value assocation map, with
 // optional explicit comparator.
@@ -50,29 +51,22 @@ struct is_associative_container<Seq, void_t<impl::assoc_test<Seq>>>: impl::assoc
 //   1. the sequence is an lvalue reference, and
 //   2. if the deduced return type from calling `get` on an entry from the sequence is an lvalue reference.
 
-namespace impl {
-    // import std::get for ADL below.
+namespace maputil_impl {
+    // import std::get and std::begin for ADL below.
+    using std::begin;
     using std::get;
 
-    // TODO: C++14 use std::equal_to<void> for this.
-    struct generic_equal_to {
-        template <typename A, typename B>
-        bool operator()(A&& a, B&& b) {
-            return std::forward<A>(a)==std::forward<B>(b);
-        }
-    };
-
     // use linear search
     template <
         typename Seq,
         typename Key,
-        typename Eq = generic_equal_to,
-        typename Ret0 = decltype(get<1>(*std::begin(std::declval<Seq&&>()))),
-        typename Ret = typename std::conditional<
+        typename Eq = std::equal_to<>,
+        typename Ret0 = decltype(get<1>(*begin(std::declval<Seq&&>()))),
+        typename Ret = std::conditional_t<
             std::is_rvalue_reference<Seq&&>::value || !std::is_lvalue_reference<Ret0>::value,
-            typename std::remove_reference<Ret0>::type,
+            std::remove_reference_t<Ret0>,
             Ret0
-        >::type
+        >
     >
     optional<Ret> value_by_key(std::false_type, Seq&& seq, const Key& key, Eq eq=Eq{}) {
         for (auto&& entry: seq) {
@@ -89,11 +83,11 @@ namespace impl {
         typename Key,
         typename FindRet = decltype(std::declval<Assoc&&>().find(std::declval<Key>())),
         typename Ret0 = decltype(get<1>(*std::declval<FindRet>())),
-        typename Ret = typename std::conditional<
+        typename Ret = std::conditional_t<
             std::is_rvalue_reference<Assoc&&>::value || !std::is_lvalue_reference<Ret0>::value,
-            typename std::remove_reference<Ret0>::type,
+            std::remove_reference_t<Ret0>,
             Ret0
-        >::type
+        >
     >
     optional<Ret> value_by_key(std::true_type, Assoc&& map, const Key& key) {
         auto it = map.find(key);
@@ -105,15 +99,16 @@ namespace impl {
 }
 
 template <typename C, typename Key, typename Eq>
-auto value_by_key(C&& c, const Key& k, Eq eq)
-   DEDUCED_RETURN_TYPE(impl::value_by_key(std::false_type{}, std::forward<C>(c), k, eq))
+auto value_by_key(C&& c, const Key& k, Eq eq) {
+    return maputil_impl::value_by_key(std::false_type{}, std::forward<C>(c), k, eq);
+}
 
 template <typename C, typename Key>
-auto value_by_key(C&& c, const Key& k)
-    DEDUCED_RETURN_TYPE(
-        impl::value_by_key(
-            std::integral_constant<bool, is_associative_container<C>::value>{},
-            std::forward<C>(c), k))
+auto value_by_key(C&& c, const Key& k) {
+    return maputil_impl::value_by_key(
+        std::integral_constant<bool, is_associative_container<C>::value>{},
+        std::forward<C>(c), k);
+}
 
 // Find the index into an ordered sequence of a value by binary search;
 // returns optional<size_type> for the size_type associated with the sequence.
@@ -123,7 +118,7 @@ template <typename C, typename Key>
 optional<typename sequence_traits<C>::difference_type> binary_search_index(const C& c, const Key& key) {
     auto strict = strict_view(c);
     auto it = std::lower_bound(strict.begin(), strict.end(), key);
-    return it!=strict.end() && key==*it? util::just(std::distance(strict.begin(), it)): util::nullopt;
+    return it!=strict.end() && key==*it? just(std::distance(strict.begin(), it)): nullopt;
 }
 
 // Key equality helper for NUL-terminated strings.
diff --git a/arbor/util/meta.hpp b/arbor/util/meta.hpp
index f1debc43..957c3451 100644
--- a/arbor/util/meta.hpp
+++ b/arbor/util/meta.hpp
@@ -6,33 +6,18 @@
 #include <iterator>
 #include <type_traits>
 
-
-#include "util/deduce_return.hpp"
-
 namespace arb {
 namespace util {
 
 // The following classes and functions can be replaced
 // with std functions when we migrate to later versions of C++.
 //
-// C++14:
-// result_of_t, enable_if_t, decay_t, size, cbegin, cend.
-//
 // C++17:
 // void_t, empty, data, as_const
 
-template <typename T>
-using result_of_t = typename std::result_of<T>::type;
-
-template <bool V, typename R = void>
-using enable_if_t = typename std::enable_if<V, R>::type;
-
 template <class...>
 using void_t = void;
 
-template <typename T>
-using decay_t = typename std::decay<T>::type;
-
 template <typename X>
 constexpr std::size_t size(const X& x) { return x.size(); }
 
@@ -40,10 +25,10 @@ template <typename X, std::size_t N>
 constexpr std::size_t size(X (&)[N]) noexcept { return N; }
 
 template <typename C>
-constexpr auto data(C& c) -> decltype(c.data()) { return c.data(); }
+constexpr auto data(C& c) { return c.data(); }
 
 template <typename C>
-constexpr auto data(const C& c) -> decltype(c.data()) { return c.data(); }
+constexpr auto data(const C& c) { return c.data(); }
 
 template <typename T, std::size_t N>
 constexpr T* data(T (&a)[N]) noexcept { return a; }
@@ -52,29 +37,10 @@ template <typename T>
 void as_const(T&& t) = delete;
 
 template <typename T>
-constexpr typename std::add_const<T>::type& as_const(T& t) {
+constexpr std::add_const_t<T>& as_const(T& t) {
     return t;
 }
 
-// Wrap cbegin, cend in inner namespace in order to properly invoke ADL.
-
-namespace impl {
-    using std::begin;
-    using std::end;
-
-    template <typename T>
-    constexpr auto cbegin_(const T& c) DEDUCED_RETURN_TYPE(begin(c))
-
-    template <typename T>
-    constexpr auto cend_(const T& c) DEDUCED_RETURN_TYPE(end(c))
-}
-
-template <typename T>
-constexpr auto cbegin(const T& c) DEDUCED_RETURN_TYPE(impl::cbegin_(c))
-
-template <typename T>
-constexpr auto cend(const T& c) DEDUCED_RETURN_TYPE(impl::cend_(c))
-
 // Use sequence `empty() const` method if exists, otherwise
 // compare begin and end.
 
@@ -122,9 +88,12 @@ namespace impl_seqtrait {
     template <typename Seq, typename = void>
     struct data_returns_pointer: std::false_type {};
 
+    template <typename T, std::size_t N>
+    struct data_returns_pointer<T (&)[N], void>: public std::true_type {};
+
     template <typename T>
-    struct data_returns_pointer<T, void_t<decltype(util::data(std::declval<T>()))>>:
-        public std::is_pointer<decltype(util::data(std::declval<T>()))>::type {};
+    struct data_returns_pointer<T, void_t<decltype(std::declval<T>().data())>>:
+        public std::is_pointer<decltype(std::declval<T>().data())>::type {};
 
     template <typename Seq>
     struct sequence_traits {
@@ -139,41 +108,65 @@ namespace impl_seqtrait {
         using const_sentinel = decltype(end(std::declval<const Seq&>()));
 
         static constexpr bool is_contiguous = data_returns_pointer<Seq>::value;
+        static constexpr bool is_regular = std::is_same<iterator, sentinel>::value;
     };
+
+    template<typename T, typename V=void>
+    struct is_sequence:
+        std::false_type {};
+
+    template<typename T>
+    struct is_sequence<T, void_t<decltype(begin(std::declval<T>()))>>:
+        std::true_type {};
+
 }
 
 template <typename Seq>
 using sequence_traits = impl_seqtrait::sequence_traits<Seq>;
 
+// Sequence test by checking begin.
+
+template <typename T>
+using is_sequence = impl_seqtrait::is_sequence<T>;
+
+template <typename T>
+using enable_if_sequence_t = std::enable_if_t<util::is_sequence<T>::value>;
+
+template <typename T>
+using is_contiguous = std::integral_constant<bool, sequence_traits<T>::is_contiguous>;
+
+template <typename T>
+using is_regular_sequence = std::integral_constant<bool, sequence_traits<T>::is_regular>;
+
 // Convenience short cuts for `enable_if`
 
 template <typename T>
 using enable_if_copy_constructible_t =
-    enable_if_t<std::is_copy_constructible<T>::value>;
+    std::enable_if_t<std::is_copy_constructible<T>::value>;
 
 template <typename T>
 using enable_if_move_constructible_t =
-    enable_if_t<std::is_move_constructible<T>::value>;
+    std::enable_if_t<std::is_move_constructible<T>::value>;
 
 template <typename T>
 using enable_if_default_constructible_t =
-    enable_if_t<std::is_default_constructible<T>::value>;
+    std::enable_if_t<std::is_default_constructible<T>::value>;
 
 template <typename... T>
 using enable_if_constructible_t =
-    enable_if_t<std::is_constructible<T...>::value>;
+    std::enable_if_t<std::is_constructible<T...>::value>;
 
 template <typename T>
 using enable_if_copy_assignable_t =
-    enable_if_t<std::is_copy_assignable<T>::value>;
+    std::enable_if_t<std::is_copy_assignable<T>::value>;
 
 template <typename T>
 using enable_if_move_assignable_t =
-    enable_if_t<std::is_move_assignable<T>::value>;
+    std::enable_if_t<std::is_move_assignable<T>::value>;
 
 template <typename T>
 using enable_if_trivially_copyable_t =
-    enable_if_t<std::is_trivially_copyable<T>::value>;
+    std::enable_if_t<std::is_trivially_copyable<T>::value>;
 
 // Iterator class test
 // (might not be portable before C++17)
@@ -194,7 +187,7 @@ template <typename T, typename = void>
 struct is_random_access_iterator: public std::false_type {};
 
 template <typename T>
-struct is_random_access_iterator<T, enable_if_t<
+struct is_random_access_iterator<T, std::enable_if_t<
         std::is_same<
             std::random_access_iterator_tag,
             typename std::iterator_traits<T>::iterator_category>::value
@@ -209,7 +202,7 @@ template <typename T, typename = void>
 struct is_bidirectional_iterator: public std::false_type {};
 
 template <typename T>
-struct is_bidirectional_iterator<T, enable_if_t<
+struct is_bidirectional_iterator<T, std::enable_if_t<
         std::is_same<
             std::random_access_iterator_tag,
             typename std::iterator_traits<T>::iterator_category>::value
@@ -228,7 +221,7 @@ template <typename T, typename = void>
 struct is_forward_iterator: public std::false_type {};
 
 template <typename T>
-struct is_forward_iterator<T, enable_if_t<
+struct is_forward_iterator<T, std::enable_if_t<
         std::is_same<
             std::random_access_iterator_tag,
             typename std::iterator_traits<T>::iterator_category>::value
@@ -254,13 +247,13 @@ struct common_random_access_iterator<
     I,
     E,
     void_t<decltype(false? std::declval<I>(): std::declval<E>())>,
-    util::enable_if_t<
+    std::enable_if_t<
         is_random_access_iterator<
-            decay_t<decltype(false? std::declval<I>(): std::declval<E>())>
+            std::decay_t<decltype(false? std::declval<I>(): std::declval<E>())>
         >::value
     >
 > {
-    using type = util::decay_t<
+    using type = std::decay_t<
         decltype(false ? std::declval<I>() : std::declval<E>())
     >;
 };
@@ -276,17 +269,6 @@ template <typename I, typename E>
 struct has_common_random_access_iterator<I, E, void_t<util::common_random_access_iterator_t<I, E>>>:
     std::true_type {};
 
-template<typename T, typename V=void>
-struct is_sequence:
-    std::false_type {};
-
-template<typename T>
-struct is_sequence<T, void_t<decltype(std::begin(std::declval<T>()))>>:
-    std::true_type {};
-
-template <typename T>
-using enable_if_sequence_t = util::enable_if_t<util::is_sequence<T>::value>;
-
 // No generic lambdas in C++11, so some convenience accessors for pairs that
 // are type-generic
 
diff --git a/arbor/util/partition.hpp b/arbor/util/partition.hpp
index 0f1324a0..0f360fd2 100644
--- a/arbor/util/partition.hpp
+++ b/arbor/util/partition.hpp
@@ -99,7 +99,7 @@ private:
 template <
     typename Seq,
     typename SeqIter = typename sequence_traits<Seq>::const_iterator,
-    typename = enable_if_t<is_forward_iterator<SeqIter>::value>
+    typename = std::enable_if_t<is_forward_iterator<SeqIter>::value>
 >
 partition_range<SeqIter> partition_view(const Seq& r) {
     return partition_range<SeqIter>(r);
diff --git a/arbor/util/partition_iterator.hpp b/arbor/util/partition_iterator.hpp
index 1f23f718..8b187e56 100644
--- a/arbor/util/partition_iterator.hpp
+++ b/arbor/util/partition_iterator.hpp
@@ -31,7 +31,7 @@ public:
     const I& inner() const { return inner_; }
     I& inner() { return inner_; }
 
-    using inner_value_type = decay_t<decltype(*inner_)>;
+    using inner_value_type = std::decay_t<decltype(*inner_)>;
 
     using typename base::difference_type;
     using value_type = std::pair<inner_value_type, inner_value_type>;
@@ -42,7 +42,7 @@ public:
 
     template <
         typename J,
-        typename = enable_if_t<!std::is_same<decay_t<J>, partition_iterator>::value>
+        typename = std::enable_if_t<!std::is_same<std::decay_t<J>, partition_iterator>::value>
     >
     explicit partition_iterator(J&& c): inner_{std::forward<J>(c)} {}
 
diff --git a/arbor/util/range.hpp b/arbor/util/range.hpp
index a47537bd..fbec6108 100644
--- a/arbor/util/range.hpp
+++ b/arbor/util/range.hpp
@@ -47,7 +47,7 @@ struct range {
     using sentinel = S;
     using const_iterator = iterator;
     using difference_type = typename std::iterator_traits<iterator>::difference_type;
-    using size_type = typename std::make_unsigned<difference_type>::type;
+    using size_type = std::make_unsigned_t<difference_type>;
     using value_type = typename std::iterator_traits<iterator>::value_type;
     using reference = typename std::iterator_traits<iterator>::reference;
     using const_reference = const value_type&;
@@ -67,7 +67,7 @@ struct range {
     template <
         typename U1,
         typename U2,
-        typename = enable_if_t<
+        typename = std::enable_if_t<
             std::is_constructible<iterator, U1>::value &&
             std::is_constructible<sentinel, U2>::value>
     >
@@ -94,7 +94,7 @@ struct range {
     sentinel cend() const { return right; }
 
     template <typename V = iterator>
-    enable_if_t<is_forward_iterator<V>::value, size_type>
+    std::enable_if_t<is_forward_iterator<V>::value, size_type>
     size() const {
         return util::distance(begin(), end());
     }
@@ -108,18 +108,18 @@ struct range {
         std::swap(right, other.right);
     }
 
-    auto front() const -> decltype(*left) { return *left; }
+    decltype(auto) front() const { return *left; }
 
-    auto back() const -> decltype(*left) { return *upto(left, right); }
+    decltype(auto) back() const { return *upto(left, right); }
 
     template <typename V = iterator>
-    enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
+    std::enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
     operator[](difference_type n) const {
         return *std::next(begin(), n);
     }
 
     template <typename V = iterator>
-    enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
+    std::enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
     at(difference_type n) const {
         if (size_type(n) >= size()) {
             throw std::out_of_range("out of range in range");
@@ -129,7 +129,7 @@ struct range {
 
     // Expose `data` method if a pointer range.
     template <typename V = iterator, typename W = sentinel>
-    enable_if_t<std::is_same<V, W>::value && std::is_pointer<V>::value, iterator>
+    std::enable_if_t<std::is_same<V, W>::value && std::is_pointer<V>::value, iterator>
     data() const {
         return left;
     }
@@ -137,7 +137,7 @@ struct range {
 #ifdef ARB_HAVE_TBB
     template <
         typename V = iterator,
-        typename = enable_if_t<is_forward_iterator<V>::value>
+        typename = std::enable_if_t<is_forward_iterator<V>::value>
     >
     range(range& r, tbb::split):
         left(r.left), right(r.right)
@@ -148,7 +148,7 @@ struct range {
 
     template <
         typename V = iterator,
-        typename = enable_if_t<is_forward_iterator<V>::value>
+        typename = std::enable_if_t<is_forward_iterator<V>::value>
     >
     range(range& r, tbb::proportional_split p):
         left(r.left), right(r.right)
@@ -184,37 +184,28 @@ range<U, V> make_range(const std::pair<U, V>& iterators) {
 // Present a possibly sentinel-terminated range as an STL-compatible sequence
 // using the sentinel_iterator adaptor.
 
-// TODO: ADL begin/end with C++14 deduced return.
 template <typename Seq>
-auto canonical_view(Seq& s) ->
-    range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>>
-{
-    return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))};
-}
+auto canonical_view(Seq&& s) {
+    using std::begin;
+    using std::end;
 
-template <typename Seq>
-auto canonical_view(const Seq& s) ->
-    range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>>
-{
-    return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))};
+    return make_range(
+        make_sentinel_iterator(begin(s), end(s)),
+        make_sentinel_end(begin(s), end(s)));
 }
 
 // Strictly evaluate end point in sentinel-terminated range and present as a range over
 // iterators. Note: O(N) behaviour with forward iterator ranges or sentinel-terminated ranges.
 
 template <typename Seq>
-auto strict_view(Seq&& s) -> range<decltype(std::begin(s))>
-{
-    return make_range(std::begin(s), std::begin(s)==std::end(s)? std::begin(s): std::next(util::upto(std::begin(s), std::end(s))));
-}
+auto strict_view(Seq&& s) {
+    using std::begin;
+    using std::end;
 
-#if 0
-template <typename Seq>
-auto strict_view(const Seq& s) -> range<decltype(std::begin(s))>
-{
-    return make_range(std::begin(s), std::begin(s)==std::end(s)? std::begin(s): std::next(util::upto(std::begin(s), std::end(s))));
+    auto b = begin(s);
+    auto e = end(s);
+    return make_range(b, b==e? b: std::next(util::upto(b, e)));
 }
-#endif
 
 } // namespace util
 } // namespace arb
diff --git a/arbor/util/rangeutil.hpp b/arbor/util/rangeutil.hpp
index 1979380c..25e74a0c 100644
--- a/arbor/util/rangeutil.hpp
+++ b/arbor/util/rangeutil.hpp
@@ -10,11 +10,9 @@
 #include <ostream>
 #include <numeric>
 
-#include <util/deduce_return.hpp>
-#include <util/meta.hpp>
-#include <util/range.hpp>
-#include <util/transform.hpp>
-#include <util/meta.hpp>
+#include "util/meta.hpp"
+#include "util/range.hpp"
+#include "util/transform.hpp"
 
 namespace arb {
 namespace util {
@@ -39,9 +37,10 @@ range_view(Seq&& seq) {
     return make_range(std::begin(seq), std::end(seq));
 }
 
-template <typename Seq, typename = enable_if_t<sequence_traits<Seq&&>::is_contiguous>>
-auto range_pointer_view(Seq&& seq)
-    DEDUCED_RETURN_TYPE(make_range(util::data(seq), util::data(seq)+util::size(seq)))
+template <typename Seq, typename = std::enable_if_t<sequence_traits<Seq&&>::is_contiguous>>
+auto range_pointer_view(Seq&& seq) {
+    return make_range(util::data(seq), util::data(seq)+util::size(seq));
+}
 
 template <
     typename Seq,
@@ -49,7 +48,7 @@ template <
     typename Offset2,
     typename Iter = typename sequence_traits<Seq&&>::iterator
 >
-enable_if_t<is_forward_iterator<Iter>::value, range<Iter>>
+std::enable_if_t<is_forward_iterator<Iter>::value, range<Iter>>
 subrange_view(Seq&& seq, Offset1 bi, Offset2 ei) {
     Iter b = std::begin(seq);
     std::advance(b, bi);
@@ -65,7 +64,7 @@ template <
     typename Offset2,
     typename Iter = typename sequence_traits<Seq&&>::iterator
 >
-enable_if_t<is_forward_iterator<Iter>::value, range<Iter>>
+std::enable_if_t<is_forward_iterator<Iter>::value, range<Iter>>
 subrange_view(Seq&& seq, std::pair<Offset1, Offset2> index) {
     return subrange_view(std::forward<Seq>(seq), index.first, index.second);
 }
@@ -88,7 +87,7 @@ void fill(Seq&& seq, const V& value) {
 template <typename Container, typename Seq>
 Container& append(Container &c, const Seq& seq) {
     auto canon = canonical_view(seq);
-    c.insert(c.end(), std::begin(canon), std::end(canon));
+    c.insert(c.end(), canon.begin(), canon.end());
     return c;
 }
 
@@ -139,28 +138,28 @@ AssignableContainer& assign_by(AssignableContainer& c, const Seq& seq, const Pro
 // Note that a const range reference may wrap non-const iterators.
 
 template <typename Seq>
-enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
+std::enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
 sort(Seq&& seq) {
     auto canon = canonical_view(seq);
-    std::sort(std::begin(canon), std::end(canon));
+    std::sort(canon.begin(), canon.end());
 }
 
 template <typename Seq, typename Less>
-enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
+std::enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
 sort(Seq&& seq, const Less& less) {
     auto canon = canonical_view(seq);
-    std::sort(std::begin(canon), std::end(canon), less);
+    std::sort(canon.begin(), canon.end(), less);
 }
 
 // Sort in-place by projection `proj`
 
 template <typename Seq, typename Proj>
-enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
+std::enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
 sort_by(Seq&& seq, const Proj& proj) {
     using value_type = typename sequence_traits<Seq&&>::value_type;
     auto canon = canonical_view(seq);
 
-    std::sort(std::begin(canon), std::end(canon),
+    std::sort(canon.begin(), canon.end(),
         [&proj](const value_type& a, const value_type& b) {
             return proj(a) < proj(b);
         });
@@ -169,12 +168,12 @@ sort_by(Seq&& seq, const Proj& proj) {
 // Stable sort in-place by projection `proj`
 
 template <typename Seq, typename Proj>
-enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
+std::enable_if_t<!std::is_const<typename sequence_traits<Seq&&>::reference>::value>
 stable_sort_by(Seq&& seq, const Proj& proj) {
     using value_type = typename sequence_traits<Seq&&>::value_type;
     auto canon = canonical_view(seq);
 
-    std::stable_sort(std::begin(canon), std::end(canon),
+    std::stable_sort(canon.begin(), canon.end(),
         [&proj](const value_type& a, const value_type& b) {
             return proj(a) < proj(b);
         });
@@ -185,13 +184,13 @@ stable_sort_by(Seq&& seq, const Proj& proj) {
 template <typename Seq, typename Predicate>
 bool all_of(const Seq& seq, const Predicate& pred) {
     auto canon = canonical_view(seq);
-    return std::all_of(std::begin(canon), std::end(canon), pred);
+    return std::all_of(canon.begin(), canon.end(), pred);
 }
 
 template <typename Seq, typename Predicate>
 bool any_of(const Seq& seq, const Predicate& pred) {
     auto canon = canonical_view(seq);
-    return std::any_of(std::begin(canon), std::end(canon), pred);
+    return std::any_of(canon.begin(), canon.end(), pred);
 }
 
 // Accumulate by projection `proj`
@@ -203,7 +202,7 @@ template <
 >
 Value sum_by(const Seq& seq, const Proj& proj, Value base = Value{}) {
     auto canon = canonical_view(transform_view(seq, proj));
-    return std::accumulate(std::begin(canon), std::end(canon), base);
+    return std::accumulate(canon.begin(), canon.end(), base);
 }
 
 // Maximum element by projection `proj`
@@ -216,7 +215,7 @@ max_element_by(Seq&& seq, const Proj& proj) {
     using value_type = typename sequence_traits<Seq&&>::value_type;
     auto canon = canonical_view(seq);
 
-    return std::max_element(std::begin(canon), std::end(canon),
+    return std::max_element(canon.begin(), canon.end(),
         [&proj](const value_type& a, const value_type& b) {
             return proj(a) < proj(b);
         });
@@ -237,12 +236,15 @@ template <
     typename Compare = std::less<Value>
 >
 Value max_value(const Seq& seq, Compare cmp = Compare{}) {
+    using std::begin;
+    using std::end;
+
     if (util::empty(seq)) {
         return Value{};
     }
 
-    auto i = std::begin(seq);
-    auto e = std::end(seq);
+    auto i = begin(seq);
+    auto e = end(seq);
     Value m = *i;
     while (++i!=e) {
         Value x = *i;
@@ -261,12 +263,15 @@ template <
     typename Compare = std::less<Value>
 >
 std::pair<Value, Value> minmax_value(const Seq& seq, Compare cmp = Compare{}) {
+    using std::begin;
+    using std::end;
+
     if (util::empty(seq)) {
         return {Value{}, Value{}};
     }
 
-    auto i = std::begin(seq);
-    auto e = std::end(seq);
+    auto i = begin(seq);
+    auto e = end(seq);
     Value lower = *i;
     Value upper = *i;
     while (++i!=e) {
@@ -286,7 +291,7 @@ std::pair<Value, Value> minmax_value(const Seq& seq, Compare cmp = Compare{}) {
 template <typename Seq, typename = util::enable_if_sequence_t<const Seq&>>
 bool is_sorted(const Seq& seq) {
     auto canon = canonical_view(seq);
-    return std::is_sorted(std::begin(canon), std::end(canon));
+    return std::is_sorted(canon.begin(), canon.end());
 }
 
 
@@ -297,11 +302,14 @@ bool is_sorted(const Seq& seq) {
 template <
     typename Seq,
     typename Proj,
-    typename Compare = std::less<typename std::result_of<Proj (typename sequence_traits<const Seq&>::value_type)>::type>
+    typename Compare = std::less<std::result_of_t<Proj (typename sequence_traits<const Seq&>::value_type)>>
 >
 bool is_sorted_by(const Seq& seq, const Proj& proj, Compare cmp = Compare{}) {
-    auto i = std::begin(seq);
-    auto e = std::end(seq);
+    using std::begin;
+    using std::end;
+
+    auto i = begin(seq);
+    auto e = end(seq);
 
     if (i==e) {
         return true;
diff --git a/arbor/util/scope_exit.hpp b/arbor/util/scope_exit.hpp
index 2bd619fb..5f83678a 100644
--- a/arbor/util/scope_exit.hpp
+++ b/arbor/util/scope_exit.hpp
@@ -14,7 +14,7 @@ namespace util {
 
 template <
     typename F,
-    typename = typename std::enable_if<std::is_nothrow_move_constructible<F>::value>::type
+    typename = std::enable_if_t<std::is_nothrow_move_constructible<F>::value>
 >
 class scope_exit {
     F on_exit;
@@ -23,7 +23,7 @@ class scope_exit {
 public:
     template <
         typename F2,
-        typename = typename std::enable_if<std::is_nothrow_constructible<F, F2>::value>::type
+        typename = std::enable_if_t<std::is_nothrow_constructible<F, F2>::value>
     >
     explicit scope_exit(F2&& f) noexcept:
         on_exit(std::forward<F2>(f)) {}
@@ -44,8 +44,8 @@ public:
 };
 
 template <typename F>
-scope_exit<typename std::decay<F>::type> on_scope_exit(F&& f) {
-    return scope_exit<typename std::decay<F>::type>(std::forward<F>(f));
+scope_exit<std::decay_t<F>> on_scope_exit(F&& f) {
+    return scope_exit<std::decay_t<F>>(std::forward<F>(f));
 }
 
 } // namespace util
diff --git a/arbor/util/sentinel.hpp b/arbor/util/sentinel.hpp
index 110dc567..09b98513 100644
--- a/arbor/util/sentinel.hpp
+++ b/arbor/util/sentinel.hpp
@@ -58,7 +58,7 @@ public:
 
     sentinel_iterator(I i): e_(i) {}
 
-    template <typename V = S, typename = enable_if_t<!std::is_same<I, V>::value>>
+    template <typename V = S, typename = std::enable_if_t<!std::is_same<I, V>::value>>
     sentinel_iterator(S i): e_(i) {}
 
     sentinel_iterator() = default;
@@ -70,7 +70,7 @@ public:
 
     // forward and input iterator requirements
 
-    auto operator*() const -> decltype(*(this->iter())) { return *iter(); }
+    decltype(auto) operator*() const { return *iter(); }
 
     I operator->() const { return e_.template ptr<0>(); }
 
@@ -141,7 +141,7 @@ public:
         return iter()-x.iter();
     }
 
-    auto operator[](difference_type n) const -> decltype(*(this->iter())) {
+    decltype(auto) operator[](difference_type n) const {
         return *(iter()+n);
     }
 
@@ -174,7 +174,7 @@ public:
 
 template <typename I, typename S>
 using sentinel_iterator_t =
-    typename std::conditional<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>::type;
+    std::conditional_t<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>;
 
 template <typename I, typename S>
 sentinel_iterator_t<I, S> make_sentinel_iterator(const I& i, const S& s) {
diff --git a/arbor/util/span.hpp b/arbor/util/span.hpp
index 7a572cbc..817e1acf 100644
--- a/arbor/util/span.hpp
+++ b/arbor/util/span.hpp
@@ -7,10 +7,9 @@
 #include <type_traits>
 #include <utility>
 
-#include <util/counter.hpp>
-#include <util/deduce_return.hpp>
-#include <util/meta.hpp>
-#include <util/range.hpp>
+#include "util/counter.hpp"
+#include "util/meta.hpp"
+#include "util/range.hpp"
 
 namespace arb {
 namespace util {
@@ -24,13 +23,13 @@ template <typename I>
 using span = range<counter<I>>;
 
 template <typename I, typename J>
-span<typename std::common_type<I, J>::type> make_span(I left, J right) {
-    return span<typename std::common_type<I, J>::type>(left, right);
+span<std::common_type_t<I, J>> make_span(I left, J right) {
+    return span<std::common_type_t<I, J>>(left, right);
 }
 
 template <typename I, typename J>
-span<typename std::common_type<I, J>::type> make_span(std::pair<I, J> interval) {
-    return span<typename std::common_type<I, J>::type>(interval.first, interval.second);
+span<std::common_type_t<I, J>> make_span(std::pair<I, J> interval) {
+    return span<std::common_type_t<I, J>>(interval.first, interval.second);
 }
 
 template <typename I>
@@ -39,7 +38,9 @@ span<I> make_span(I right) {
 }
 
 template <typename Seq>
-auto count_along(const Seq& s) DEDUCED_RETURN_TYPE(util::make_span(util::size(s)))
+auto count_along(const Seq& s) {
+    return util::make_span(util::size(s));
+}
 
 } // namespace util
 } // namespace arb
diff --git a/arbor/util/transform.hpp b/arbor/util/transform.hpp
index c91dbba7..ed436973 100644
--- a/arbor/util/transform.hpp
+++ b/arbor/util/transform.hpp
@@ -35,16 +35,16 @@ class transform_iterator: public iterator_adaptor<transform_iterator<I, F>, I> {
     I& inner() { return inner_; }
 
     using inner_value_type = decltype(*inner_);
-    using raw_value_type = typename std::result_of<F (inner_value_type)>::type;
+    using raw_value_type = std::result_of_t<F (inner_value_type)>;
 
     static constexpr bool present_lvalue = std::is_reference<raw_value_type>::value;
 
 
 public:
     using typename base::difference_type;
-    using value_type = util::decay_t<raw_value_type>;
-    using pointer = typename std::conditional<present_lvalue, value_type*, const value_type*>::type;
-    using reference = typename std::conditional<present_lvalue, raw_value_type, const value_type&>::type;
+    using value_type = std::decay_t<raw_value_type>;
+    using pointer = std::conditional_t<present_lvalue, value_type*, const value_type*>;
+    using reference = std::conditional_t<present_lvalue, raw_value_type, const value_type&>;
 
     transform_iterator() = default;
 
@@ -80,17 +80,17 @@ public:
 
     // forward and input iterator requirements
 
-    typename std::conditional<present_lvalue, reference, value_type>::type
+    std::conditional_t<present_lvalue, reference, value_type>
     operator*() const {
         return f_.cref()(*inner_);
     }
 
-    typename std::conditional<present_lvalue, pointer, util::pointer_proxy<value_type>>::type
+    std::conditional_t<present_lvalue, pointer, util::pointer_proxy<value_type>>
     operator->() const {
         return pointer_impl(std::integral_constant<bool, present_lvalue>{});
     }
 
-    typename std::conditional<present_lvalue, reference, value_type>::type
+    std::conditional_t<present_lvalue, reference, value_type>
     operator[](difference_type n) const {
         return *(*this+n);
     }
@@ -121,32 +121,31 @@ private:
 };
 
 template <typename I, typename F>
-transform_iterator<I, util::decay_t<F>> make_transform_iterator(const I& i, const F& f) {
-    return transform_iterator<I, util::decay_t<F>>(i, f);
+transform_iterator<I, std::decay_t<F>> make_transform_iterator(const I& i, const F& f) {
+    return transform_iterator<I, std::decay_t<F>>(i, f);
 }
 
-template <
-    typename Seq,
-    typename F,
-    typename seq_iter = typename sequence_traits<Seq>::iterator,
-    typename seq_sent = typename sequence_traits<Seq>::sentinel,
-    typename = enable_if_t<std::is_same<seq_iter, seq_sent>::value>
->
-range<transform_iterator<seq_iter, util::decay_t<F>>>
-transform_view(Seq&& s, const F& f) {
-    return {make_transform_iterator(std::begin(s), f), make_transform_iterator(std::end(s), f)};
+// TODO C++17: simplify with constexpr-if
+namespace transform_impl {
+    using std::begin;
+    using std::end;
+
+    // transform over regular sequences:
+    template <typename Seq, typename F>
+    auto transform_(Seq&& s, const F& f, std::true_type) {
+        return make_range(make_transform_iterator(begin(s), f), make_transform_iterator(end(s), f));
+    }
+
+    // transform over sentinel-terminated sequences:
+    template <typename Seq, typename F>
+    auto transform_(Seq&& s, const F& f, std::false_type) {
+        return make_range(make_transform_iterator(begin(s), f), end(s));
+    }
 }
 
-template <
-    typename Seq,
-    typename F,
-    typename seq_iter = typename sequence_traits<Seq>::iterator,
-    typename seq_sent = typename sequence_traits<Seq>::sentinel,
-    typename = enable_if_t<!std::is_same<seq_iter, seq_sent>::value>
->
-range<transform_iterator<seq_iter, util::decay_t<F>>, seq_sent>
-transform_view(Seq&& s, const F& f) {
-    return {make_transform_iterator(std::begin(s), f), std::end(s)};
+template <typename Seq, typename F>
+auto transform_view(Seq&& s, const F& f) {
+    return transform_impl::transform_(std::forward<Seq>(s), f, is_regular_sequence<Seq&&>{});
 }
 
 } // namespace util
diff --git a/aux/tinyopt.hpp b/aux/tinyopt.hpp
index 058bd2cc..8bb6fc24 100644
--- a/aux/tinyopt.hpp
+++ b/aux/tinyopt.hpp
@@ -69,7 +69,7 @@ auto keywords(const KeywordPairs& pairs) -> keyword_parser<decltype(std::begin(p
     return keyword_parser<decltype(std::begin(pairs)->second)>(pairs);
 }
 
-template <typename V = std::string, typename P = default_parser<V>, typename = typename std::enable_if<!std::is_same<V, void>::value>::type>
+template <typename V = std::string, typename P = default_parser<V>, typename = std::enable_if_t<!std::is_same<V, void>::value>>
 optional<V> parse_opt(char **& argp, char shortopt, const char* longopt=nullptr, const P& parse = P{}) {
     const char* arg = argp[0];
 
diff --git a/example/brunel/brunel_miniapp.cpp b/example/brunel/brunel_miniapp.cpp
index ef07bf2c..ae716caf 100644
--- a/example/brunel/brunel_miniapp.cpp
+++ b/example/brunel/brunel_miniapp.cpp
@@ -15,7 +15,6 @@
 #include <arbor/recipe.hpp>
 #include <arbor/simulation.hpp>
 #include <arbor/threadinfo.hpp>
-#include <arbor/util/make_unique.hpp>
 #include <arbor/version.hpp>
 
 #include "json_meter.hpp"
@@ -242,7 +241,7 @@ int main(int argc, char** argv) {
         brunel_recipe recipe(nexc, ninh, next, in_degree_prop, w, d, rel_inh_strength, poiss_lambda, seed);
 
         auto register_exporter = [] (const io::cl_options& options) {
-            return util::make_unique<io::exporter_spike_file>
+            return std::make_unique<io::exporter_spike_file>
                        (options.file_name, options.output_path,
                         options.file_extension, options.over_write);
         };
diff --git a/example/brunel/io.cpp b/example/brunel/io.cpp
index c4940320..82624cd7 100644
--- a/example/brunel/io.cpp
+++ b/example/brunel/io.cpp
@@ -10,7 +10,6 @@
 #include <tclap/CmdLine.h>
 #include <arbor/util/optional.hpp>
 
-#include "util/meta.hpp"
 #include "io.hpp"
 
 // Let TCLAP understand value arguments that are of an optional type.
@@ -69,7 +68,7 @@ namespace arb {
         template <
         typename T,
         typename Arg,
-        typename = util::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
+        typename = std::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
         >
         static void update_option(T& opt, Arg& arg) {
             if (arg.isSet()) {
diff --git a/example/miniapp/io.cpp b/example/miniapp/io.cpp
index e17cfdfc..ac171ca5 100644
--- a/example/miniapp/io.cpp
+++ b/example/miniapp/io.cpp
@@ -13,9 +13,6 @@
 
 #include <arbor/util/optional.hpp>
 
-#include "util/meta.hpp"
-#include "util/strprintf.hpp"
-
 #include "io.hpp"
 
 // Let TCLAP understand value arguments that are of an optional type.
@@ -77,7 +74,7 @@ public:
 template <
     typename T,
     typename Arg,
-    typename = util::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
+    typename = std::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
 >
 static void update_option(T& opt, Arg& arg) {
     if (arg.isSet()) {
diff --git a/example/miniapp/miniapp.cpp b/example/miniapp/miniapp.cpp
index 2fb8be9c..c1095b08 100644
--- a/example/miniapp/miniapp.cpp
+++ b/example/miniapp/miniapp.cpp
@@ -82,7 +82,7 @@ int main(int argc, char** argv) {
 
         auto register_exporter = [] (const io::cl_options& options) {
             return
-                util::make_unique<io::exporter_spike_file>(
+                std::make_unique<io::exporter_spike_file>(
                     options.file_name, options.output_path,
                     options.file_extension, options.over_write);
         };
diff --git a/include/arbor/common_types.hpp b/include/arbor/common_types.hpp
index 923cf6b6..418ba786 100644
--- a/include/arbor/common_types.hpp
+++ b/include/arbor/common_types.hpp
@@ -21,7 +21,7 @@ using cell_gid_type = std::uint32_t;
 
 // For sizes of collections of cells.
 
-using cell_size_type = typename std::make_unsigned<cell_gid_type>::type;
+using cell_size_type = std::make_unsigned_t<cell_gid_type>;
 
 // For indexes into cell-local data.
 //
@@ -32,7 +32,7 @@ using cell_lid_type = std::uint32_t;
 
 // For counts of cell-local data.
 
-using cell_local_size_type = typename std::make_unsigned<cell_lid_type>::type;
+using cell_local_size_type = std::make_unsigned_t<cell_lid_type>;
 
 // For global identification of an item of cell local data.
 //
diff --git a/include/arbor/constants.hpp b/include/arbor/constants.hpp
index a761f369..45a38752 100644
--- a/include/arbor/constants.hpp
+++ b/include/arbor/constants.hpp
@@ -14,8 +14,6 @@ namespace constant {
 // 2010    8.3144621   96485.3365
 // 2014    8.3144598   96485.33289
 
-// TODO: use value templates from C++14
-
 // Universal gas constant (R)
 // https://physics.nist.gov/cgi-bin/cuu/Value?r
 constexpr double gas_constant = 8.3144598;  //  J.K^-1.mol^-1
diff --git a/include/arbor/generic_event.hpp b/include/arbor/generic_event.hpp
index 77ef4328..5eb3c25a 100644
--- a/include/arbor/generic_event.hpp
+++ b/include/arbor/generic_event.hpp
@@ -44,27 +44,27 @@
 namespace arb {
 
 template <typename Event>
-auto event_time(const Event& ev) -> decltype(ev.time) {
+auto event_time(const Event& ev) {
     return ev.time;
 }
 
 template <typename Event>
-auto event_index(const Event& ev) -> decltype(ev.index) {
+auto event_index(const Event& ev) {
     return ev.index;
 }
 
 template <typename Event>
-auto event_data(const Event& ev) -> decltype(ev.data) {
+auto event_data(const Event& ev) {
     return ev.data;
 }
 
 struct event_time_less {
-    template <typename T, typename Event, typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
+    template <typename T, typename Event, typename = std::enable_if_t<std::is_floating_point<T>::value>>
     bool operator() (T l, const Event& r) {
         return l<event_time(r);
     }
 
-    template <typename T, typename Event, typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
+    template <typename T, typename Event, typename = std::enable_if_t<std::is_floating_point<T>::value>>
     bool operator() (const Event& l, T r) {
         return event_time(l)<r;
     }
diff --git a/include/arbor/mc_segment.hpp b/include/arbor/mc_segment.hpp
index c28ec316..7a0a7c28 100644
--- a/include/arbor/mc_segment.hpp
+++ b/include/arbor/mc_segment.hpp
@@ -14,7 +14,6 @@
 #include <arbor/morphology.hpp>
 #include <arbor/mechinfo.hpp>
 #include <arbor/point.hpp>
-#include <arbor/util/make_unique.hpp>
 #include <arbor/util/optional.hpp>
 
 namespace arb {
@@ -176,7 +175,7 @@ public:
 
     std::unique_ptr<mc_segment> clone() const override {
         // use default copy constructor
-        return util::make_unique<placeholder_segment>(*this);
+        return std::make_unique<placeholder_segment>(*this);
     }
 
     bool is_placeholder() const override
@@ -201,7 +200,7 @@ public:
 
     std::unique_ptr<mc_segment> clone() const override {
         // use default copy constructor
-        return util::make_unique<soma_segment>(*this);
+        return std::make_unique<soma_segment>(*this);
     }
 
     value_type radius() const
@@ -284,7 +283,7 @@ public:
 
     std::unique_ptr<mc_segment> clone() const override {
         // use default copy constructor
-        return util::make_unique<cable_segment>(*this);
+        return std::make_unique<cable_segment>(*this);
     }
 
     value_type length() const
diff --git a/include/arbor/mechinfo.hpp b/include/arbor/mechinfo.hpp
index 2ee9faa9..41a3c631 100644
--- a/include/arbor/mechinfo.hpp
+++ b/include/arbor/mechinfo.hpp
@@ -11,7 +11,6 @@
 #include <vector>
 
 #include <arbor/ion.hpp>
-#include <arbor/util/enumhash.hpp>
 
 namespace arb {
 
@@ -30,19 +29,6 @@ struct mechanism_field_spec {
     double upper_bound = std::numeric_limits<double>::max();
 
     bool valid(double x) const { return x>=lower_bound && x<=upper_bound; }
-
-    // TODO: C++14 - no need for ctor below, as aggregate initialization
-    // will work with default member initializers.
-
-    mechanism_field_spec(
-        enum field_kind kind = parameter,
-        std::string units = "",
-        double default_value = 0.,
-        double lower_bound = std::numeric_limits<double>::lowest(),
-        double upper_bound = std::numeric_limits<double>::max()
-     ):
-        kind(kind), units(units), default_value(default_value), lower_bound(lower_bound), upper_bound(upper_bound)
-    {}
 };
 
 struct ion_dependency {
@@ -71,7 +57,7 @@ struct mechanism_info {
     std::unordered_map<std::string, mechanism_field_spec> state;
 
     // Ion dependencies.
-    std::unordered_map<ionKind, ion_dependency, util::enum_hash> ions;
+    std::unordered_map<ionKind, ion_dependency> ions;
 
     mechanism_fingerprint fingerprint;
 };
diff --git a/include/arbor/simd/avx512.hpp b/include/arbor/simd/avx512.hpp
index dd723c3f..0f062d11 100644
--- a/include/arbor/simd/avx512.hpp
+++ b/include/arbor/simd/avx512.hpp
@@ -344,7 +344,7 @@ struct avx512_int8: implbase<avx512_int8> {
     using is_int8_simd = std::integral_constant<bool, std::is_same<int, typename Impl::scalar_type>::value && Impl::width==8>;
 
     template <typename ImplIndex,
-              typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+              typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static __m512i gather(tag<ImplIndex>, const int32* p, const typename ImplIndex::vector_type& index) {
         int32 o[16];
         ImplIndex::copy_to(index, o);
@@ -353,7 +353,7 @@ struct avx512_int8: implbase<avx512_int8> {
     }
 
     template <typename ImplIndex,
-              typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+              typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static __m512i gather(tag<ImplIndex>, const __m512i& a, const int32* p, const typename ImplIndex::vector_type& index, const __mmask8& mask) {
         int32 o[16];
         ImplIndex::copy_to(index, o);
@@ -362,7 +362,7 @@ struct avx512_int8: implbase<avx512_int8> {
     }
 
     template <typename ImplIndex,
-              typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+              typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static void scatter(tag<ImplIndex>, const __m512i& s, int32* p, const typename ImplIndex::vector_type& index) {
         int32 o[16];
         ImplIndex::copy_to(index, o);
@@ -371,7 +371,7 @@ struct avx512_int8: implbase<avx512_int8> {
     }
 
     template <typename ImplIndex,
-              typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+              typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static void scatter(tag<ImplIndex>, const __m512i& s, int32* p, const typename ImplIndex::vector_type& index, const __mmask8& mask) {
         int32 o[16];
         ImplIndex::copy_to(index, o);
@@ -527,7 +527,7 @@ struct avx512_double8: implbase<avx512_double8> {
     template <typename Impl>
     using is_int8_simd = std::integral_constant<bool, std::is_same<int, typename Impl::scalar_type>::value && Impl::width==8>;
 
-    template <typename ImplIndex, typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+    template <typename ImplIndex, typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static __m512d gather(tag<ImplIndex>, const double* p, const typename ImplIndex::vector_type& index) {
         int o[8];
         ImplIndex::copy_to(index, o);
@@ -535,7 +535,7 @@ struct avx512_double8: implbase<avx512_double8> {
         return _mm512_i32gather_pd(_mm256_loadu_si256(op), p, 8);
     }
 
-    template <typename ImplIndex, typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+    template <typename ImplIndex, typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static __m512d gather(tag<ImplIndex>, const __m512d& a, const double* p, const typename ImplIndex::vector_type& index, const __mmask8& mask) {
         int o[8];
         ImplIndex::copy_to(index, o);
@@ -543,7 +543,7 @@ struct avx512_double8: implbase<avx512_double8> {
         return _mm512_mask_i32gather_pd(a, mask, _mm256_loadu_si256(op), p, 8);
     }
 
-    template <typename ImplIndex, typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+    template <typename ImplIndex, typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static void scatter(tag<ImplIndex>, const __m512d& s, double* p, const typename ImplIndex::vector_type& index) {
         int o[8];
         ImplIndex::copy_to(index, o);
@@ -551,7 +551,7 @@ struct avx512_double8: implbase<avx512_double8> {
         _mm512_i32scatter_pd(p, _mm256_loadu_si256(op), s, 8);
     }
 
-    template <typename ImplIndex, typename = typename std::enable_if<is_int8_simd<ImplIndex>::value>::type>
+    template <typename ImplIndex, typename = std::enable_if_t<is_int8_simd<ImplIndex>::value>>
     static void scatter(tag<ImplIndex>, const __m512d& s, double* p, const typename ImplIndex::vector_type& index, const __mmask8& mask) {
         int o[8];
         ImplIndex::copy_to(index, o);
diff --git a/include/arbor/simd/implbase.hpp b/include/arbor/simd/implbase.hpp
index 3722ae58..ebdf6552 100644
--- a/include/arbor/simd/implbase.hpp
+++ b/include/arbor/simd/implbase.hpp
@@ -31,6 +31,7 @@
 #include <cmath>
 #include <algorithm>
 #include <iterator>
+#include <type_traits>
 
 // Derived class I must at minimum provide:
 //
diff --git a/include/arbor/simd/simd.hpp b/include/arbor/simd/simd.hpp
index 2bb0c0bb..fff3ff08 100644
--- a/include/arbor/simd/simd.hpp
+++ b/include/arbor/simd/simd.hpp
@@ -108,18 +108,18 @@ namespace simd_detail {
         }
 
         // Construct from a different SIMD value by casting.
-        template <typename Other, typename = typename std::enable_if<width==simd_traits<Other>::width>::type>
+        template <typename Other, typename = std::enable_if_t<width==simd_traits<Other>::width>>
         explicit simd_impl(const simd_impl<Other>& x) {
             value_ = Impl::cast_from(tag<Other>{}, x.value_);
         }
 
         // Construct from indirect expression (gather).
-        template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+        template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
         explicit simd_impl(indirect_expression<IndexImpl, scalar_type> pi) {
             copy_from(pi);
         }
 
-        template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+        template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
         explicit simd_impl(indirect_expression<IndexImpl, const scalar_type> pi) {
             copy_from(pi);
         }
@@ -147,7 +147,7 @@ namespace simd_detail {
             Impl::copy_to(value_, p);
         }
 
-        template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+        template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
         void copy_to(indirect_expression<IndexImpl, scalar_type> pi) const {
             Impl::scatter(tag<IndexImpl>{}, value_, pi.p, pi.index);
         }
@@ -156,7 +156,7 @@ namespace simd_detail {
             value_ = Impl::copy_from(p);
         }
 
-        template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+        template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
         void copy_from(indirect_expression<IndexImpl, scalar_type> pi) {
             switch (pi.constraint) {
             case index_constraint::none:
@@ -181,7 +181,7 @@ namespace simd_detail {
             }
         }
 
-        template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+        template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
         void copy_from(indirect_expression<IndexImpl, const scalar_type> pi) {
             switch (pi.constraint) {
             case index_constraint::none:
@@ -384,12 +384,12 @@ namespace simd_detail {
 
             // Gather and scatter.
 
-            template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+            template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
             void copy_from(indirect_expression<IndexImpl, scalar_type> pi) {
                 data_.value_ = Impl::gather(tag<IndexImpl>{}, data_.value_, pi.p, pi.index, mask_.value_);
             }
 
-            template <typename IndexImpl, typename = typename std::enable_if<width==simd_traits<IndexImpl>::width>::type>
+            template <typename IndexImpl, typename = std::enable_if_t<width==simd_traits<IndexImpl>::width>>
             void copy_to(indirect_expression<IndexImpl, scalar_type> pi) const {
                 Impl::scatter(tag<IndexImpl>{}, data_.value_, pi.p, pi.index, mask_.value_);
             }
@@ -581,7 +581,7 @@ namespace simd_detail {
         static constexpr unsigned N = simd_traits<ImplTo>::width;
         using scalar_type = typename simd_traits<ImplTo>::scalar_type;
 
-        template <typename ImplFrom, typename = typename std::enable_if<N==simd_traits<ImplFrom>::width>::type>
+        template <typename ImplFrom, typename = std::enable_if_t<N==simd_traits<ImplFrom>::width>>
         static simd_impl<ImplTo> cast(const simd_impl<ImplFrom>& v) {
             return simd_impl<ImplTo>(v);
         }
@@ -595,10 +595,10 @@ namespace simd_detail {
     struct simd_cast_impl<std::array<V, N>> {
         template <
             typename ImplFrom,
-            typename = typename std::enable_if<
+            typename = std::enable_if_t<
                 N==simd_traits<ImplFrom>::width &&
                 std::is_same<V, typename simd_traits<ImplFrom>::scalar_type>::value
-            >::type
+            >
         >
         static std::array<V, N> cast(const simd_impl<ImplFrom>& s) {
             std::array<V, N> a;
@@ -654,7 +654,7 @@ To simd_cast(const From& s) {
 template <
     typename IndexImpl,
     typename PtrLike,
-    typename V = typename std::remove_reference<decltype(*std::declval<PtrLike>())>::type
+    typename V = std::remove_reference_t<decltype(*std::declval<PtrLike>())>
 >
 simd_detail::indirect_expression<IndexImpl, V> indirect(
     PtrLike p,
diff --git a/include/arbor/simple_sampler.hpp b/include/arbor/simple_sampler.hpp
index 480f59cd..2814eb25 100644
--- a/include/arbor/simple_sampler.hpp
+++ b/include/arbor/simple_sampler.hpp
@@ -24,7 +24,7 @@ struct trace_entry {
 template <typename V>
 using trace_data = std::vector<trace_entry<V>>;
 
-template <typename V, typename = typename std::enable_if<std::is_trivially_copyable<V>::value>::type>
+template <typename V, typename = std::enable_if_t<std::is_trivially_copyable<V>::value>>
 class simple_sampler {
 public:
     explicit simple_sampler(trace_data<V>& trace): trace_(trace) {}
diff --git a/include/arbor/simulation.hpp b/include/arbor/simulation.hpp
index c7844e93..844380c6 100644
--- a/include/arbor/simulation.hpp
+++ b/include/arbor/simulation.hpp
@@ -17,7 +17,9 @@ namespace arb {
 
 using spike_export_function = std::function<void(const std::vector<spike>&)>;
 
-struct simulation_state;
+// simulation_state comprises private implentation for simulation class.
+class simulation_state;
+
 class simulation {
 public:
     simulation(const recipe& rec, const domain_decomposition& decomp, const distributed_context* ctx);
diff --git a/include/arbor/time_sequence.hpp b/include/arbor/time_sequence.hpp
index 5b0fb9ef..dff057c5 100644
--- a/include/arbor/time_sequence.hpp
+++ b/include/arbor/time_sequence.hpp
@@ -32,9 +32,8 @@ public:
 
     template <
         typename Impl,
-        typename = typename std::enable_if<
-            !std::is_same<typename std::decay<Impl>::type,
-                          time_seq>::value>::type
+        typename = std::enable_if_t<
+            !std::is_same<std::decay_t<Impl>, time_seq>::value>
     >
     time_seq(Impl&& impl):
         impl_(new wrap<Impl>(std::forward<Impl>(impl)))
diff --git a/include/arbor/util/any.hpp b/include/arbor/util/any.hpp
index 1854f06d..7198f072 100644
--- a/include/arbor/util/any.hpp
+++ b/include/arbor/util/any.hpp
@@ -38,10 +38,10 @@ public:
 
     template <
         typename T,
-        typename = typename std::enable_if<!std::is_same<typename std::decay<T>::type, any>::value>::type
+        typename = std::enable_if_t<!std::is_same<std::decay_t<T>, any>::value>
     >
     any(T&& other) {
-        using contained_type = typename std::decay<T>::type;
+        using contained_type = std::decay_t<T>;
         static_assert(std::is_copy_constructible<contained_type>::value,
             "Type of contained object stored in any must satisfy the CopyConstructible requirements.");
 
@@ -60,10 +60,10 @@ public:
 
     template <
         typename T,
-        typename = typename std::enable_if<!std::is_same<typename std::decay<T>::type, any>::value>::type
+        typename = std::enable_if_t<!std::is_same<std::decay_t<T>, any>::value>
     >
     any& operator=(T&& other) {
-        using contained_type = typename std::decay<T>::type;
+        using contained_type = std::decay_t<T>;
 
         static_assert(std::is_copy_constructible<contained_type>::value,
             "Type of contained object stored in any must satisfy the CopyConstructible requirements.");
@@ -134,8 +134,7 @@ protected:
 namespace impl {
 
 template <typename T>
-using any_cast_remove_qual = typename
-    std::remove_cv<typename std::remove_reference<T>::type>::type;
+using any_cast_remove_qual = std::remove_cv_t<std::remove_reference_t<T>>;
 
 } // namespace impl
 
diff --git a/include/arbor/util/any_ptr.hpp b/include/arbor/util/any_ptr.hpp
index a8a05bc7..2587511b 100644
--- a/include/arbor/util/any_ptr.hpp
+++ b/include/arbor/util/any_ptr.hpp
@@ -52,7 +52,7 @@ struct any_ptr {
         type_ptr_ = &typeid(T*);
     }
 
-    template <typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type>
+    template <typename T, typename = std::enable_if_t<std::is_pointer<T>::value>>
     T as() const noexcept {
         if (std::is_same<T, void*>::value) {
             return (T)ptr_;
diff --git a/include/arbor/util/enumhash.hpp b/include/arbor/util/enumhash.hpp
deleted file mode 100644
index e50d7bf8..00000000
--- a/include/arbor/util/enumhash.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <functional>
-#include <type_traits>
-
-// Work around for C++11 defect #2148: hashing enums should be supported directly by std::hash.
-// Fixed in C++14.
-
-namespace arb {
-namespace util {
-
-struct enum_hash {
-    template <typename E, typename V = typename std::underlying_type<E>::type>
-    std::size_t operator()(E e) const noexcept {
-        return std::hash<V>{}(static_cast<V>(e));
-    }
-};
-
-} // namespace util
-} // namespace arb
diff --git a/include/arbor/util/make_unique.hpp b/include/arbor/util/make_unique.hpp
deleted file mode 100644
index af0b80b0..00000000
--- a/include/arbor/util/make_unique.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-#include <memory>
-
-namespace arb {
-namespace util {
-
-// TODO: Remove when migrate to C++14
-
-template <typename T, typename... Args>
-std::unique_ptr<T> make_unique(Args&&... args) {
-    return std::unique_ptr<T>(new T(std::forward<Args>(args) ...));
-}
-
-} // namespace util
-} // namespace arb
-
-
diff --git a/include/arbor/util/optional.hpp b/include/arbor/util/optional.hpp
index 0822ef0b..f36d3b39 100644
--- a/include/arbor/util/optional.hpp
+++ b/include/arbor/util/optional.hpp
@@ -77,7 +77,7 @@ namespace detail {
     struct optional_tag {};
 
     template <typename X>
-    using is_optional = std::is_base_of<optional_tag, typename std::decay<X>::type>;
+    using is_optional = std::is_base_of<optional_tag, std::decay_t<X>>;
 
     template <typename D, typename X>
     struct wrapped_type_impl {
@@ -91,7 +91,7 @@ namespace detail {
 
     template <typename X>
     struct wrapped_type {
-        using type = typename wrapped_type_impl<typename std::decay<X>::type, X>::type;
+        using type = typename wrapped_type_impl<std::decay_t<X>, X>::type;
     };
 
     template <typename X>
@@ -169,7 +169,7 @@ namespace detail {
 
     // type utilities
     template <typename T>
-    using enable_unless_optional_t = typename std::enable_if<!is_optional<T>::value>::type;
+    using enable_unless_optional_t = std::enable_if_t<!is_optional<T>::value>;
 
     // avoid nonnull address warnings when using operator| with e.g. char array constants
     template <typename T>
@@ -251,10 +251,10 @@ struct optional: detail::optional_base<X> {
 
     template <
         typename Y = X,
-        typename = typename std::enable_if<
+        typename = std::enable_if_t<
             std::is_move_assignable<Y>::value &&
             std::is_move_constructible<Y>::value
-        >::type
+        >
     >
     optional& operator=(optional&& o) {
         if (set) {
diff --git a/include/arbor/util/uninitialized.hpp b/include/arbor/util/uninitialized.hpp
index 23455ff1..7752f2c2 100644
--- a/include/arbor/util/uninitialized.hpp
+++ b/include/arbor/util/uninitialized.hpp
@@ -19,11 +19,11 @@ namespace util {
 
 template <typename T>
 using enable_if_copy_constructible_t =
-    typename std::enable_if<std::is_copy_constructible<T>::value>::type;
+    std::enable_if_t<std::is_copy_constructible<T>::value>;
 
 template <typename... T>
 using enable_if_constructible_t =
-    typename std::enable_if<std::is_constructible<T...>::value>::type;
+    std::enable_if_t<std::is_constructible<T...>::value>;
 
 /*
  * Maintains storage for a value of type X, with explicit
@@ -32,7 +32,7 @@ using enable_if_constructible_t =
 template <typename X>
 class uninitialized {
 private:
-    typename std::aligned_storage<sizeof(X), alignof(X)>::type data;
+    std::aligned_storage_t<sizeof(X), alignof(X)> data;
 
 public:
     using pointer = X*;
@@ -74,11 +74,11 @@ public:
 
     // Apply the one-parameter functor F to the value by reference.
     template <typename F>
-    typename std::result_of<F(reference)>::type apply(F&& f) { return f(ref()); }
+    std::result_of_t<F(reference)> apply(F&& f) { return f(ref()); }
 
     // Apply the one-parameter functor F to the value by const reference.
     template <typename F>
-    typename std::result_of<F(const_reference)>::type apply(F&& f) const { return f(cref()); }
+    std::result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); }
 };
 
 /*
diff --git a/include/arbor/util/unique_any.hpp b/include/arbor/util/unique_any.hpp
index cae9edbe..d5b72f6f 100644
--- a/include/arbor/util/unique_any.hpp
+++ b/include/arbor/util/unique_any.hpp
@@ -56,7 +56,7 @@ public:
 
     template <
         typename T,
-        typename = typename std::enable_if<!std::is_same<typename std::decay<T>::type, unique_any>::value>::type
+        typename = std::enable_if_t<!std::is_same<std::decay_t<T>, unique_any>::value>
     >
     unique_any(T&& other) {
         state_.reset(new model<contained_type<T>>(std::forward<T>(other)));
@@ -69,7 +69,7 @@ public:
 
     template <
         typename T,
-        typename = typename std::enable_if<!std::is_same<typename std::decay<T>::type, unique_any>::value>::type
+        typename = std::enable_if_t<!std::is_same<std::decay_t<T>, unique_any>::value>
     >
     unique_any& operator=(T&& other) {
         state_.reset(new model<contained_type<T>>(std::forward<T>(other)));
@@ -94,7 +94,7 @@ public:
 
 private:
     template <typename T>
-    using contained_type = typename std::decay<T>::type;
+    using contained_type = std::decay_t<T>;
 
     struct interface {
         virtual ~interface() = default;
diff --git a/lmorpho/lsys_models.cpp b/lmorpho/lsys_models.cpp
index 06cd740a..41caf7e1 100644
--- a/lmorpho/lsys_models.cpp
+++ b/lmorpho/lsys_models.cpp
@@ -1,9 +1,9 @@
-#include <math.hpp>
+#include <cmath>
 
 #include "lsystem.hpp"
 #include "lsys_models.hpp"
 
-static constexpr double inf = arb::math::infinity<double>();
+static constexpr double inf = INFINITY;
 
 // Predefined parameters for two classes of neurons. Numbers taken primarily
 // from Ascoli et al. 2001, but some details (soma diameters for example)
diff --git a/lmorpho/lsystem.cpp b/lmorpho/lsystem.cpp
index feddc70c..e2ceee43 100644
--- a/lmorpho/lsystem.cpp
+++ b/lmorpho/lsystem.cpp
@@ -5,7 +5,6 @@
 #include <vector>
 
 #include <arbor/morphology.hpp>
-
 #include "math.hpp"
 
 #include "lsystem.hpp"
diff --git a/modcc/functionexpander.cpp b/modcc/functionexpander.cpp
index 7cbc3545..aedf9f16 100644
--- a/modcc/functionexpander.cpp
+++ b/modcc/functionexpander.cpp
@@ -1,4 +1,5 @@
 #include <iostream>
+#include <memory>
 
 #include "astmanip.hpp"
 #include "error.hpp"
@@ -18,7 +19,7 @@ expression_ptr insert_unique_local_assignment(expr_list_type& stmts, Expression*
 
 expr_list_type lower_function_calls(Expression* e)
 {
-    auto v = make_unique<FunctionCallLowerer>(e->scope());
+    auto v = std::make_unique<FunctionCallLowerer>(e->scope());
 
     if(auto a=e->is_assignment()) {
 #ifdef LOGGING
diff --git a/modcc/modccutil.hpp b/modcc/modccutil.hpp
index 7dd73f19..58dc7d1a 100644
--- a/modcc/modccutil.hpp
+++ b/modcc/modccutil.hpp
@@ -159,8 +159,3 @@ std::ostream& operator<< (std::ostream& os, std::vector<T> const& V) {
     return os << "]";
 }
 
-template <typename T, typename... Args>
-std::unique_ptr<T> make_unique(Args&&... args) {
-    return std::unique_ptr<T>(new T(std::forward<Args>(args) ...));
-}
-
diff --git a/modcc/module.cpp b/modcc/module.cpp
index 409845d4..dbabd329 100644
--- a/modcc/module.cpp
+++ b/modcc/module.cpp
@@ -2,6 +2,7 @@
 #include <cassert>
 #include <fstream>
 #include <iostream>
+#include <memory>
 #include <set>
 #include <unordered_set>
 
@@ -312,13 +313,13 @@ bool Module::semantic() {
 
             switch(solve_expression->method()) {
             case solverMethod::cnexp:
-                solver = make_unique<CnexpSolverVisitor>();
+                solver = std::make_unique<CnexpSolverVisitor>();
                 break;
             case solverMethod::sparse:
-                solver = make_unique<SparseSolverVisitor>();
+                solver = std::make_unique<SparseSolverVisitor>();
                 break;
             case solverMethod::none:
-                solver = make_unique<DirectSolverVisitor>();
+                solver = std::make_unique<DirectSolverVisitor>();
                 break;
             }
 
diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp
index cd9f413c..0d64f56a 100644
--- a/modcc/printer/infoprinter.cpp
+++ b/modcc/printer/infoprinter.cpp
@@ -23,14 +23,14 @@ std::ostream& operator<<(std::ostream& out, const id_field_info& wrap) {
     const Id& id = wrap.id;
 
     out << "{" << quote(id.name()) << ", "
-        << "spec(spec::" << wrap.kind << ", " << quote(id.unit_string()) << ", "
+        << "{spec::" << wrap.kind << ", " << quote(id.unit_string()) << ", "
         << (id.has_value()? id.value: "0");
 
     if (id.has_range()) {
         out << ", " << id.range.first.spelling << "," << id.range.second.spelling;
     }
 
-    out << ")}";
+    out << "}}";
     return out;
 }
 
diff --git a/modcc/token.hpp b/modcc/token.hpp
index 3fd5bb0b..97667c2e 100644
--- a/modcc/token.hpp
+++ b/modcc/token.hpp
@@ -84,16 +84,6 @@ enum class tok {
     reserved, // placeholder for generating keyword lookup
 };
 
-namespace std {
-    // note: necessary before C++14 (refer: lwg dr#2148).
-    template <>
-    struct hash<tok> {
-        std::size_t operator()(const tok& x) const {
-            return std::hash<int>()(static_cast<int>(x));
-        }
-    };
-}
-
 // what is in a token?
 //  tok indicating type of token
 //  information about its location
diff --git a/test/unit-distributed/distributed_listener.hpp b/test/unit-distributed/distributed_listener.hpp
index 6c61f8b4..acf3c1a8 100644
--- a/test/unit-distributed/distributed_listener.hpp
+++ b/test/unit-distributed/distributed_listener.hpp
@@ -60,6 +60,9 @@ private:
         printer(std::string base_name, int rank);
     };
 
+    template <typename T>
+    friend printer& operator<<(printer&, const T&);
+
     const arb::distributed_context* context_;
     int rank_;
     int size_;
diff --git a/test/unit-modcc/test_printers.cpp b/test/unit-modcc/test_printers.cpp
index 8ba6a2b8..8dbbedaf 100644
--- a/test/unit-modcc/test_printers.cpp
+++ b/test/unit-modcc/test_printers.cpp
@@ -1,3 +1,4 @@
+#include <memory>
 #include <regex>
 #include <string>
 #include <sstream>
@@ -6,6 +7,7 @@
 
 #include "printer/cexpr_emit.hpp"
 #include "printer/cprinter.hpp"
+#include "printer/cudaprinter.hpp"
 #include "expression.hpp"
 #include "symdiff.hpp"
 
@@ -89,7 +91,7 @@ TEST(scalar_printer, statement) {
         {
             SCOPED_TRACE("CPrinter");
             std::stringstream out;
-            auto printer = make_unique<CPrinter>(out);
+            auto printer = std::make_unique<CPrinter>(out);
             e->accept(printer.get());
             std::string text = out.str();
 
@@ -97,20 +99,16 @@ TEST(scalar_printer, statement) {
             EXPECT_EQ(strip(tc.expected), strip(text));
         }
 
-#if 0
         {
-            SCOPED_TRACE("CUDAPrinter");
-            TextBuffer buf;
-            auto printer = make_unique<CUDAPrinter>();
-            printer->set_buffer(buf);
-
+            SCOPED_TRACE("CudaPrinter");
+            std::stringstream out;
+            auto printer = std::make_unique<CudaPrinter>(out);
             e->accept(printer.get());
-            std::string text = buf.str();
+            std::string text = out.str();
 
             verbose_print(e->to_string(), " :--: ", text);
             EXPECT_EQ(strip(tc.expected), strip(text));
         }
-#endif
     }
 }
 
@@ -150,7 +148,7 @@ TEST(CPrinter, proc_body) {
 
         proc->semantic(globals);
         std::stringstream out;
-        auto v = make_unique<CPrinter>(out);
+        auto v = std::make_unique<CPrinter>(out);
         proc->is_procedure()->body()->accept(v.get());
         std::string text = out.str();
 
diff --git a/test/unit/common.hpp b/test/unit/common.hpp
index 7f9b73d9..46e3f5cd 100644
--- a/test/unit/common.hpp
+++ b/test/unit/common.hpp
@@ -13,15 +13,6 @@
 
 namespace testing {
 
-// String ctor suffix (until C++14!).
-
-namespace string_literals {
-    inline std::string operator ""_s(const char* s, std::size_t n) {
-        return std::string(s, n);
-    }
-}
-
-
 // Sentinel for C-style strings, for use with range-related tests.
 
 struct null_terminated_t {
diff --git a/test/unit/test_algorithms.cpp b/test/unit/test_algorithms.cpp
index 7a0e8aa6..8a2aef6d 100644
--- a/test/unit/test_algorithms.cpp
+++ b/test/unit/test_algorithms.cpp
@@ -780,7 +780,7 @@ TEST(algorithms, binary_find)
         auto itv = binary_find(vr, 10);
         auto found = itv!=std::end(vr);
         EXPECT_TRUE(found);
-        EXPECT_EQ(std::distance(arb::util::cbegin(v), itv), 1u);
+        EXPECT_EQ(std::distance(std::cbegin(v), itv), 1u);
         if (found) {
             EXPECT_EQ(*itv, 10);
         }
diff --git a/test/unit/test_any.cpp b/test/unit/test_any.cpp
index 03dd829a..5085bb1c 100644
--- a/test/unit/test_any.cpp
+++ b/test/unit/test_any.cpp
@@ -1,3 +1,4 @@
+#include <string>
 #include <type_traits>
 #include <typeinfo>
 
@@ -7,8 +8,8 @@
 #include "../gtest.h"
 #include "common.hpp"
 
+using namespace std::string_literals;
 using namespace arb;
-using namespace testing::string_literals;
 
 TEST(any, copy_construction) {
     util::any any_int(2);
@@ -74,7 +75,7 @@ TEST(any, type) {
     using util::any;
 
     any anyi(42);
-    any anys("hello"_s);
+    any anys("hello"s);
     any anyv(std::vector<int>{1, 2, 3});
     any any0;
 
@@ -140,7 +141,7 @@ TEST(any, any_cast_ptr) {
     auto ptr_i = util::any_cast<int>(&ai);
     EXPECT_EQ(*ptr_i, 42);
 
-    util::any as("hello"_s);
+    util::any as("hello"s);
     auto ptr_s = util::any_cast<std::string>(&as);
     EXPECT_EQ(*ptr_s, "hello");
 
@@ -302,7 +303,7 @@ TEST(any, make_any) {
         // create a string from const char*
         auto a = make_any<std::string>("hello");
 
-        EXPECT_EQ(any_cast<std::string>(a), "hello"_s);
+        EXPECT_EQ(any_cast<std::string>(a), "hello"s);
     }
 
     // test that we make_any correctly forwards rvalue arguments to the constructor
diff --git a/test/unit/test_compartments.cpp b/test/unit/test_compartments.cpp
index 9d5c711f..13196d73 100644
--- a/test/unit/test_compartments.cpp
+++ b/test/unit/test_compartments.cpp
@@ -92,7 +92,7 @@ TEST(compartments, div_ends) {
         EXPECT_DOUBLE_EQ(volume_frustrum(l, r2, r1), d.volume());
 
         auto sl = l/2.0;
-        auto rc = mean(r1, r2);
+        auto rc = 0.5*(r1+r2);
 
         div_compartment expected{
             0,
diff --git a/test/unit/test_cycle.cpp b/test/unit/test_cycle.cpp
index 6cac18a8..ec935ae1 100644
--- a/test/unit/test_cycle.cpp
+++ b/test/unit/test_cycle.cpp
@@ -111,8 +111,8 @@ TEST(cycle_iterator, decrement) {
 
 TEST(cycle_iterator, carray) {
     int values[] = { 4, 2, 3 };
-    auto cycle_iter = util::make_cyclic_iterator(util::cbegin(values),
-                                                 util::cend(values));
+    auto cycle_iter = util::make_cyclic_iterator(std::cbegin(values),
+                                                 std::cend(values));
     auto values_size = util::size(values);
     for (auto i = 0u; i < 2*values_size; ++i) {
         EXPECT_EQ(values[i % values_size], *cycle_iter++);
diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp
index 2389e9f6..8e3e311d 100644
--- a/test/unit/test_fvm_layout.cpp
+++ b/test/unit/test_fvm_layout.cpp
@@ -1,3 +1,4 @@
+#include <string>
 #include <vector>
 
 #include <arbor/util/optional.hpp>
@@ -13,8 +14,8 @@
 #include "common.hpp"
 #include "../common_cells.hpp"
 
+using namespace std::string_literals;
 using namespace arb;
-using namespace testing::string_literals;
 
 using util::make_span;
 using util::count_along;
@@ -272,7 +273,7 @@ TEST(fvm_layout, area) {
 
     cable_segment* cable = cells[1].segment(2)->as_cable();
     double a = volume(cable)/cable->length();
-    EXPECT_FLOAT_EQ(math::pi<double>()*0.8*0.8/4, a);
+    EXPECT_FLOAT_EQ(math::pi<double>*0.8*0.8/4, a);
 
     double h = cable->length()/4;
     double g = a/h/cable->rL; // [µm·S/cm]
@@ -365,11 +366,11 @@ TEST(fvm_layout, synapse_targets) {
 
     auto& expsyn_cv = M.mechanisms.at("expsyn").cv;
     auto& expsyn_target = M.mechanisms.at("expsyn").target;
-    auto& expsyn_e = value_by_key(M.mechanisms.at("expsyn").param_values, "e"_s).value();
+    auto& expsyn_e = value_by_key(M.mechanisms.at("expsyn").param_values, "e"s).value();
 
     auto& exp2syn_cv = M.mechanisms.at("exp2syn").cv;
     auto& exp2syn_target = M.mechanisms.at("exp2syn").target;
-    auto& exp2syn_e = value_by_key(M.mechanisms.at("exp2syn").param_values, "e"_s).value();
+    auto& exp2syn_e = value_by_key(M.mechanisms.at("exp2syn").param_values, "e"s).value();
 
     EXPECT_TRUE(util::is_sorted(expsyn_cv));
     EXPECT_TRUE(util::is_sorted(exp2syn_cv));
@@ -538,8 +539,8 @@ TEST(fvm_layout, density_norm_area) {
     ASSERT_EQ(1u, M.mechanisms.count("hh"));
     auto& hh_params = M.mechanisms.at("hh").param_values;
 
-    auto& gkbar = value_by_key(hh_params, "gkbar"_s).value();
-    auto& gl = value_by_key(hh_params, "gl"_s).value();
+    auto& gkbar = value_by_key(hh_params, "gkbar"s).value();
+    auto& gl = value_by_key(hh_params, "gl"s).value();
 
     EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gkbar, gkbar));
     EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gl, gl));
diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp
index 56f12249..0bb0461a 100644
--- a/test/unit/test_fvm_lowered.cpp
+++ b/test/unit/test_fvm_lowered.cpp
@@ -1,3 +1,4 @@
+#include <string>
 #include <vector>
 
 #include "../gtest.h"
@@ -28,7 +29,7 @@
 #include "../common_cells.hpp"
 #include "../simple_recipes.hpp"
 
-using namespace testing::string_literals;
+using namespace std::string_literals;
 
 using backend = arb::multicore::backend;
 using fvm_cell = arb::fvm_lowered_cell_impl<backend>;
@@ -74,18 +75,13 @@ ACCESS_BIND(\
     &arb::multicore::mechanism::ion_index_table)
 
 
-// TODO: C++14 replace use with generic lambda
-struct generic_isnan {
-    template <typename V>
-    bool operator()(V& v) const { return std::isnan(v); }
-} isnan_;
-
 using namespace arb;
 
 TEST(fvm_lowered, matrix_init)
 {
-    algorithms::generic_is_positive ispos;
-    algorithms::generic_is_negative isneg;
+    auto isnan = [](auto v) { return std::isnan(v); };
+    auto ispos = [](auto v) { return v>0; };
+    auto isneg = [](auto v) { return v<0; };
 
     mc_cell cell = make_cell_ball_and_stick();
 
@@ -108,9 +104,9 @@ TEST(fvm_lowered, matrix_init)
     auto n = J.size();
     auto& mat = J.state_;
 
-    EXPECT_FALSE(util::any_of(util::subrange_view(mat.u, 1, n), isnan_));
-    EXPECT_FALSE(util::any_of(mat.d, isnan_));
-    EXPECT_FALSE(util::any_of(J.solution(), isnan_));
+    EXPECT_FALSE(util::any_of(util::subrange_view(mat.u, 1, n), isnan));
+    EXPECT_FALSE(util::any_of(mat.d, isnan));
+    EXPECT_FALSE(util::any_of(J.solution(), isnan));
 
     EXPECT_FALSE(util::any_of(util::subrange_view(mat.u, 1, n), ispos));
     EXPECT_FALSE(util::any_of(mat.d, isneg));
@@ -301,12 +297,12 @@ TEST(fvm_lowered, derived_mechs) {
         using fvec = std::vector<fvm_value_type>;
         fvec tau_values;
         for (auto& mech: fvcell.*private_mechanisms_ptr) {
-            EXPECT_EQ("test_kin1"_s, mech->internal_name());
+            EXPECT_EQ("test_kin1"s, mech->internal_name());
 
             auto cmech = dynamic_cast<multicore::mechanism*>(mech.get());
             ASSERT_TRUE(cmech);
 
-            auto opt_tau_ptr = util::value_by_key((cmech->*private_global_table_ptr)(), "tau"_s);
+            auto opt_tau_ptr = util::value_by_key((cmech->*private_global_table_ptr)(), "tau"s);
             ASSERT_TRUE(opt_tau_ptr);
             tau_values.push_back(*opt_tau_ptr.value());
         }
@@ -416,7 +412,7 @@ TEST(fvm_lowered, weighted_write_ion) {
 
     auto test_ca = dynamic_cast<multicore::mechanism*>(find_mechanism(fvcell, "test_ca"));
 
-    auto opt_cai_ptr = util::value_by_key((test_ca->*private_field_table_ptr)(), "cai"_s);
+    auto opt_cai_ptr = util::value_by_key((test_ca->*private_field_table_ptr)(), "cai"s);
     ASSERT_TRUE(opt_cai_ptr);
     auto& test_ca_cai = *opt_cai_ptr.value();
 
diff --git a/test/unit/test_maputil.cpp b/test/unit/test_maputil.cpp
index 17e86938..96ebd6d0 100644
--- a/test/unit/test_maputil.cpp
+++ b/test/unit/test_maputil.cpp
@@ -2,6 +2,7 @@
 
 #include <map>
 #include <set>
+#include <string>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
@@ -14,7 +15,7 @@
 
 using namespace arb;
 
-using namespace testing::string_literals;
+using namespace std::string_literals;
 using testing::nocopy;
 using testing::nomove;
 
@@ -163,10 +164,10 @@ TEST(maputil, value_by_key_sequence) {
 
     auto r1 = value_by_key(table, 3);
     EXPECT_TRUE(r1);
-    EXPECT_EQ("three"_s, r1.value());
+    EXPECT_EQ("three"s, r1.value());
     EXPECT_TRUE(is_optional_reference(r1));
     r1.value() = "four";
-    EXPECT_EQ("four"_s, value_by_key(table, 3).value());
+    EXPECT_EQ("four"s, value_by_key(table, 3).value());
 
     auto r2 = value_by_key(std::move(table), 1);
     EXPECT_TRUE(r2);
diff --git a/test/unit/test_math.cpp b/test/unit/test_math.cpp
index 93e2c457..7d0d777a 100644
--- a/test/unit/test_math.cpp
+++ b/test/unit/test_math.cpp
@@ -9,8 +9,8 @@ using namespace arb::math;
 
 TEST(math, pi) {
     // check regression against long double literal in implementation
-    auto pi_ld = pi<long double>();
-    auto pi_d = pi<double>();
+    auto pi_ld = pi<long double>;
+    auto pi_d = pi<double>;
 
     if (std::numeric_limits<long double>::digits>std::numeric_limits<double>::digits) {
         EXPECT_NE(0.0, pi_ld-pi_d);
@@ -55,11 +55,11 @@ TEST(math, lerp) {
 TEST(math, frustrum) {
     // cross check against cone calculation
     auto cone_area = [](double l, double r) {
-        return std::hypot(l,r)*r*pi<double>();
+        return std::hypot(l,r)*r*pi<double>;
     };
 
     auto cone_volume = [](double l, double r) {
-        return pi<double>()*square(r)*l/3.0;
+        return pi<double>*square(r)*l/3.0;
     };
 
     EXPECT_DOUBLE_EQ(cone_area(5.0, 1.3), area_frustrum(5.0, 0.0, 1.3));
@@ -81,17 +81,17 @@ TEST(math, frustrum) {
 
 TEST(math, infinity) {
     // check values for float, double, long double
-    auto finf = infinity<float>();
+    auto finf = infinity<float>;
     EXPECT_TRUE((std::is_same<float, decltype(finf)>::value));
     EXPECT_TRUE(std::isinf(finf));
     EXPECT_GT(finf, 0.f);
 
-    auto dinf = infinity<double>();
+    auto dinf = infinity<double>;
     EXPECT_TRUE((std::is_same<double, decltype(dinf)>::value));
     EXPECT_TRUE(std::isinf(dinf));
     EXPECT_GT(dinf, 0.0);
 
-    auto ldinf = infinity<long double>();
+    auto ldinf = infinity<long double>;
     EXPECT_TRUE((std::is_same<long double, decltype(ldinf)>::value));
     EXPECT_TRUE(std::isinf(ldinf));
     EXPECT_GT(ldinf, 0.0l);
@@ -101,7 +101,7 @@ TEST(math, infinity) {
         float f;
         double d;
         long double ld;
-    } check = {infinity<>(), infinity<>(), infinity<>()};
+    } check = {infinity<>, infinity<>, infinity<>};
 
     EXPECT_EQ(std::numeric_limits<float>::infinity(), check.f);
     EXPECT_EQ(std::numeric_limits<double>::infinity(), check.d);
@@ -130,10 +130,10 @@ TEST(math, signum) {
     double negzero = std::copysign(0., -1.);
     EXPECT_EQ(0, signum(negzero));
 
-    EXPECT_EQ(1, signum(infinity<double>()));
-    EXPECT_EQ(1, signum(infinity<float>()));
-    EXPECT_EQ(-1, signum(-infinity<double>()));
-    EXPECT_EQ(-1, signum(-infinity<float>()));
+    EXPECT_EQ(1, signum(infinity<double>));
+    EXPECT_EQ(1, signum(infinity<float>));
+    EXPECT_EQ(-1, signum(-infinity<double>));
+    EXPECT_EQ(-1, signum(-infinity<float>));
 }
 
 TEST(math, next_pow2) {
@@ -340,7 +340,7 @@ TEST(quaternion, assignop) {
 }
 
 TEST(quaternion, rotate) {
-    double deg_to_rad = pi<double>()/180.;
+    double deg_to_rad = pi<double>/180.;
     double sqrt3o2 = std::sqrt(3.)/2.;
     double eps = 1e-15;
 
diff --git a/test/unit/test_mc_cell.cpp b/test/unit/test_mc_cell.cpp
index 98e1b5e3..67858182 100644
--- a/test/unit/test_mc_cell.cpp
+++ b/test/unit/test_mc_cell.cpp
@@ -6,6 +6,7 @@
 #include "tree.hpp"
 
 using namespace arb;
+using ::arb::math::pi;
 
 TEST(mc_cell, soma) {
     // test that insertion of a soma works
@@ -108,14 +109,14 @@ TEST(mc_cell, multiple_cables) {
     //      volume = 1
     //      area   = 2
     auto seg = [](section_kind k) {
-        return make_segment<cable_segment>( k, 1.0, 1.0, 1./math::pi<double>() );
+        return make_segment<cable_segment>( k, 1.0, 1.0, 1./pi<double> );
     };
 
     //  add a pre-defined segment
     {
         mc_cell c;
 
-        auto soma_radius = std::pow(3./(4.*math::pi<double>()), 1./3.);
+        auto soma_radius = std::pow(3./(4.*pi<double>), 1./3.);
 
         // cell strucure as follows
         // left   :  segment numbering
@@ -160,7 +161,7 @@ TEST(mc_cell, multiple_cables) {
 TEST(mc_cell, unbranched_chain) {
     mc_cell c;
 
-    auto soma_radius = std::pow(3./(4.*math::pi<double>()), 1./3.);
+    auto soma_radius = std::pow(3./(4.*pi<double>), 1./3.);
 
     // Cell strucure that looks like a centipede: i.e. each segment has only one child
     //
@@ -172,8 +173,8 @@ TEST(mc_cell, unbranched_chain) {
     c.add_soma(soma_radius, {0,0,1});
 
     // hook the dendrite and axons
-    c.add_cable(0, make_segment<cable_segment>(section_kind::dendrite, 1.0, 1.0, 1./math::pi<double>()));
-    c.add_cable(1, make_segment<cable_segment>(section_kind::dendrite, 1.0, 1.0, 1./math::pi<double>()));
+    c.add_cable(0, make_segment<cable_segment>(section_kind::dendrite, 1.0, 1.0, 1./pi<double>));
+    c.add_cable(1, make_segment<cable_segment>(section_kind::dendrite, 1.0, 1.0, 1./pi<double>));
 
     EXPECT_EQ(c.num_segments(), 3u);
 
diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp
index 1c2a04bf..c19418f5 100644
--- a/test/unit/test_mechcat.cpp
+++ b/test/unit/test_mechcat.cpp
@@ -1,3 +1,5 @@
+#include <string>
+
 #include <arbor/arbexcept.hpp>
 #include <arbor/fvm_types.hpp>
 #include <arbor/mechanism.hpp>
@@ -6,8 +8,8 @@
 
 #include "common.hpp"
 
+using namespace std::string_literals;
 using namespace arb;
-using namespace testing::string_literals;
 
 // Set up a small system of mechanisms and backends for testing,
 // comprising:
@@ -260,7 +262,7 @@ TEST(mechcat, instance) {
     EXPECT_EQ(typeid(fleeb_bar), typeid(*special_fleeb_bar_mech.get()));
     EXPECT_EQ(typeid(fleeb_bar), typeid(*fleeb2_bar_mech.get()));
 
-    EXPECT_EQ("fleeb"_s, fleeb2_bar_mech->internal_name());
+    EXPECT_EQ("fleeb"s, fleeb2_bar_mech->internal_name());
 
     // special_fleeb and fleeb2 (deriving from special_fleeb) have a specialized
     // implementation:
@@ -275,8 +277,8 @@ TEST(mechcat, instance) {
     EXPECT_EQ(typeid(special_fleeb_foo), typeid(*special_fleeb_foo_mech.get()));
     EXPECT_EQ(typeid(special_fleeb_foo), typeid(*fleeb2_foo_mech.get()));
 
-    EXPECT_EQ("fleeb"_s, fleeb1_foo_mech->internal_name());
-    EXPECT_EQ("special fleeb"_s, fleeb2_foo_mech->internal_name());
+    EXPECT_EQ("fleeb"s, fleeb1_foo_mech->internal_name());
+    EXPECT_EQ("special fleeb"s, fleeb2_foo_mech->internal_name());
 }
 
 TEST(mechcat, instantiate) {
diff --git a/test/unit/test_multi_event_stream.cpp b/test/unit/test_multi_event_stream.cpp
index 62d51fb9..d471a7ba 100644
--- a/test/unit/test_multi_event_stream.cpp
+++ b/test/unit/test_multi_event_stream.cpp
@@ -1,9 +1,9 @@
 #include <vector>
 #include "../gtest.h"
 
-#include <backends/event.hpp>
-#include <backends/multicore/multi_event_stream.hpp>
-#include <util/rangeutil.hpp>
+#include "backends/event.hpp"
+#include "backends/multicore/multi_event_stream.hpp"
+#include "util/rangeutil.hpp"
 
 using namespace arb;
 
@@ -39,8 +39,9 @@ namespace {
 namespace {
     // convenience wrapper around marked_events:
     template <typename MultiEventStream>
-    auto marked_range(const MultiEventStream& m, unsigned i)
-        DEDUCED_RETURN_TYPE(util::make_range(m.marked_events().begin_marked(i), m.marked_events().end_marked(i)))
+    auto marked_range(const MultiEventStream& m, unsigned i) {
+        return util::make_range(m.marked_events().begin_marked(i), m.marked_events().end_marked(i));
+    }
 }
 
 TEST(multi_event_stream, init) {
diff --git a/test/unit/test_optional.cpp b/test/unit/test_optional.cpp
index 8759ea8d..c14b34de 100644
--- a/test/unit/test_optional.cpp
+++ b/test/unit/test_optional.cpp
@@ -9,8 +9,8 @@
 
 #include "common.hpp"
 
+using namespace std::string_literals;
 using namespace arb::util;
-using namespace testing::string_literals;
 
 TEST(optional, ctors) {
     optional<int> a, b(3), c = b, d = 4;
@@ -173,7 +173,7 @@ TEST(optional, ctor_nocopy) {
 
     const optional<nocopy> ccheck(nocopy(1));
     EXPECT_TRUE(std::is_rvalue_reference<decltype(std::move(ccheck).value())>::value);
-    EXPECT_TRUE(std::is_const<std::remove_reference<decltype(std::move(ccheck).value())>::type>::value);
+    EXPECT_TRUE(std::is_const<std::remove_reference_t<decltype(std::move(ccheck).value())>>::value);
 }
 
 TEST(optional, value_or) {
@@ -194,15 +194,15 @@ TEST(optional, value_or) {
     };
     check_conv cc{true};
 
-    optional<std::string> present = "present"_s;
+    optional<std::string> present = "present"s;
     optional<std::string> absent; // nullopt
 
     auto result = present.value_or(cc);
     EXPECT_EQ(typeid(std::string), typeid(result));
-    EXPECT_EQ("present"_s, result);
+    EXPECT_EQ("present"s, result);
 
     result = absent.value_or(cc);
-    EXPECT_EQ("true"_s, result);
+    EXPECT_EQ("true"s, result);
 
     // Check move semantics in argument:
 
@@ -242,7 +242,7 @@ TEST(optional, ref_value_or) {
 
     const optional<double&> cx = x;
     auto& ref3 = cx.value_or(b);
-    EXPECT_TRUE(std::is_const<std::remove_reference<decltype(ref3)>::type>::value);
+    EXPECT_TRUE(std::is_const<std::remove_reference_t<decltype(ref3)>>::value);
     EXPECT_EQ(&b, &ref3);
 }
 
diff --git a/test/unit/test_range.cpp b/test/unit/test_range.cpp
index cb0a3c63..12049ba1 100644
--- a/test/unit/test_range.cpp
+++ b/test/unit/test_range.cpp
@@ -22,9 +22,9 @@
 
 #include "common.hpp"
 
+using namespace std::string_literals;
 using namespace arb;
 
-using namespace testing::string_literals;
 using testing::null_terminated;
 using testing::nocopy;
 using testing::nomove;
@@ -315,7 +315,7 @@ TYPED_TEST_CASE_P(counter_range);
 
 TYPED_TEST_P(counter_range, max_size) {
     using int_type = TypeParam;
-    using unsigned_int_type = typename std::make_unsigned<int_type>::type;
+    using unsigned_int_type = std::make_unsigned_t<int_type>;
     using counter = util::counter<int_type>;
 
     auto l = counter{int_type{1}};
@@ -328,8 +328,8 @@ TYPED_TEST_P(counter_range, max_size) {
 
 TYPED_TEST_P(counter_range, extreme_size) {
     using int_type = TypeParam;
-    using signed_int_type = typename std::make_signed<int_type>::type;
-    using unsigned_int_type = typename std::make_unsigned<int_type>::type;
+    using signed_int_type = std::make_signed_t<int_type>;
+    using unsigned_int_type = std::make_unsigned_t<int_type>;
     using counter = util::counter<signed_int_type>;
 
     auto l = counter{std::numeric_limits<signed_int_type>::min()};
@@ -342,7 +342,7 @@ TYPED_TEST_P(counter_range, extreme_size) {
 
 TYPED_TEST_P(counter_range, size) {
     using int_type = TypeParam;
-    using signed_int_type = typename std::make_signed<int_type>::type;
+    using signed_int_type = std::make_signed_t<int_type>;
     using counter = util::counter<signed_int_type>;
 
     auto l = counter{signed_int_type{-3}};
@@ -354,7 +354,7 @@ TYPED_TEST_P(counter_range, size) {
 
 TYPED_TEST_P(counter_range, at) {
     using int_type = TypeParam;
-    using signed_int_type = typename std::make_signed<int_type>::type;
+    using signed_int_type = std::make_signed_t<int_type>;
     using counter = util::counter<signed_int_type>;
 
     auto l = counter{signed_int_type{-3}};
@@ -369,7 +369,7 @@ TYPED_TEST_P(counter_range, at) {
 
 TYPED_TEST_P(counter_range, iteration) {
     using int_type = TypeParam;
-    using signed_int_type = typename std::make_signed<int_type>::type;
+    using signed_int_type = std::make_signed_t<int_type>;
     using counter = util::counter<signed_int_type>;
 
     auto j = signed_int_type{-3};
@@ -454,11 +454,11 @@ TEST(range, sort) {
 
     // simple sort
     util::sort(util::strict_view(cstr_range));
-    EXPECT_EQ("dhowy"_s, cstr);
+    EXPECT_EQ("dhowy"s, cstr);
 
     // reverse sort by transform c to -c
     util::sort_by(util::strict_view(cstr_range), [](char c) { return -c; });
-    EXPECT_EQ("ywohd"_s, cstr);
+    EXPECT_EQ("ywohd"s, cstr);
 
     // stable sort: move capitals to front, numbers to back
     auto rank = [](char c) {
@@ -469,7 +469,7 @@ TEST(range, sort) {
     auto mixed_range = util::make_range(std::begin(mixed), null_terminated);
 
     util::stable_sort_by(util::strict_view(mixed_range), rank);
-    EXPECT_EQ("HELLOthere54321"_s, mixed);
+    EXPECT_EQ("HELLOthere54321"s, mixed);
 
 
     // sort with user-provided less comparison function
@@ -489,7 +489,7 @@ TEST(range, sum_by) {
     auto result = util::sum_by(words, prepend_);
     EXPECT_EQ("_fish_cakes_!", result);
 
-    result = util::sum_by(words, prepend_, "tasty"_s);
+    result = util::sum_by(words, prepend_, "tasty"s);
     EXPECT_EQ("tasty_fish_cakes_!", result);
 
     auto count = util::sum_by(words, [](const std::string &x) { return x.size(); });
@@ -511,10 +511,10 @@ TEST(range, all_of_any_of) {
     auto pred = [](char c) { return c=='x'? throw c:c<'5'; };
 
     // all
-    EXPECT_TRUE(util::all_of(""_s, pred));
-    EXPECT_TRUE(util::all_of("1234"_s, pred));
-    EXPECT_FALSE(util::all_of("12345"_s, pred));
-    EXPECT_FALSE(util::all_of("12345x"_s, pred));
+    EXPECT_TRUE(util::all_of(""s, pred));
+    EXPECT_TRUE(util::all_of("1234"s, pred));
+    EXPECT_FALSE(util::all_of("12345"s, pred));
+    EXPECT_FALSE(util::all_of("12345x"s, pred));
 
     EXPECT_TRUE(util::all_of(cstr(""), pred));
     EXPECT_TRUE(util::all_of(cstr("1234"), pred));
@@ -522,10 +522,10 @@ TEST(range, all_of_any_of) {
     EXPECT_FALSE(util::all_of(cstr("12345x"), pred));
 
     // any
-    EXPECT_FALSE(util::any_of(""_s, pred));
-    EXPECT_FALSE(util::any_of("8765"_s, pred));
-    EXPECT_TRUE(util::any_of("87654"_s, pred));
-    EXPECT_TRUE(util::any_of("87654x"_s, pred));
+    EXPECT_FALSE(util::any_of(""s, pred));
+    EXPECT_FALSE(util::any_of("8765"s, pred));
+    EXPECT_TRUE(util::any_of("87654"s, pred));
+    EXPECT_TRUE(util::any_of("87654x"s, pred));
 
     EXPECT_FALSE(util::any_of(cstr(""), pred));
     EXPECT_FALSE(util::any_of(cstr("8765"), pred));
@@ -549,10 +549,10 @@ TEST(range, is_sorted) {
     EXPECT_FALSE(util::is_sorted(ivec({1,2,2,1,4})));
 
     EXPECT_TRUE(util::is_sorted(cstr("abccd")));
-    EXPECT_TRUE(util::is_sorted("abccd"_s));
+    EXPECT_TRUE(util::is_sorted("abccd"s));
 
     EXPECT_FALSE(util::is_sorted(cstr("hello")));
-    EXPECT_FALSE(util::is_sorted("hello"_s));
+    EXPECT_FALSE(util::is_sorted("hello"s));
 }
 
 template <typename C>
@@ -659,7 +659,7 @@ TEST(range, reverse) {
     std::string rev;
     util::assign(rev, util::reverse_view(cstr("hello")));
 
-    EXPECT_EQ("olleh"_s, rev);
+    EXPECT_EQ("olleh"s, rev);
 }
 
 
diff --git a/test/unit/test_segment.cpp b/test/unit/test_segment.cpp
index ed6022e8..de33809d 100644
--- a/test/unit/test_segment.cpp
+++ b/test/unit/test_segment.cpp
@@ -9,8 +9,7 @@
 using namespace arb;
 
 TEST(mc_segment, kinfs) {
-    using namespace arb;
-    using arb::math::pi;
+    using ::arb::math::pi;
 
     {
         auto s = make_segment<soma_segment>(1.0);
@@ -22,7 +21,7 @@ TEST(mc_segment, kinfs) {
         EXPECT_EQ(s->kind(),   section_kind::soma);
     }
 
-    double length = 1./pi<double>();
+    double length = 1./pi<double>;
     double radius = 1.;
 
     // single cylindrical frustrum
diff --git a/test/unit/test_simd.cpp b/test/unit/test_simd.cpp
index c266b2b5..0aea5359 100644
--- a/test/unit/test_simd.cpp
+++ b/test/unit/test_simd.cpp
@@ -21,12 +21,12 @@ namespace {
     //     * other integral type => uniform_int_distribution, default interval [L, U]
     //                              such that L^2+L and U^2+U fit within the integer range.
 
-    template <typename V, typename = typename std::enable_if<std::is_floating_point<V>::value>::type>
+    template <typename V, typename = std::enable_if_t<std::is_floating_point<V>::value>>
     std::uniform_real_distribution<V> make_udist(V lb = -1., V ub = 1.) {
         return std::uniform_real_distribution<V>(lb, ub);
     }
 
-    template <typename V, typename = typename std::enable_if<std::is_integral<V>::value && !std::is_same<V, bool>::value>::type>
+    template <typename V, typename = std::enable_if_t<std::is_integral<V>::value && !std::is_same<V, bool>::value>>
     std::uniform_int_distribution<V> make_udist(
             V lb = std::numeric_limits<V>::lowest() / (2 << std::numeric_limits<V>::digits/2),
             V ub = std::numeric_limits<V>::max() >> (1+std::numeric_limits<V>::digits/2))
@@ -34,14 +34,14 @@ namespace {
         return std::uniform_int_distribution<V>(lb, ub);
     }
 
-    template <typename V, typename = typename std::enable_if<std::is_same<V, bool>::value>::type>
+    template <typename V, typename = std::enable_if_t<std::is_same<V, bool>::value>>
     std::uniform_int_distribution<> make_udist(V lb = 0, V ub = 1) {
         return std::uniform_int_distribution<>(0, 1);
     }
 
     template <typename Seq, typename Rng>
     void fill_random(Seq&& seq, Rng& rng) {
-        using V = typename std::decay<decltype(*std::begin(seq))>::type;
+        using V = std::decay_t<decltype(*std::begin(seq))>;
 
         auto u = make_udist<V>();
         for (auto& x: seq) { x = u(rng); }
@@ -49,13 +49,13 @@ namespace {
 
     template <typename Seq, typename Rng, typename B1, typename B2>
     void fill_random(Seq&& seq, Rng& rng, B1 lb, B2 ub) {
-        using V = typename std::decay<decltype(*std::begin(seq))>::type;
+        using V = std::decay_t<decltype(*std::begin(seq))>;
 
         auto u = make_udist<V>(lb, ub);
         for (auto& x: seq) { x = u(rng); }
     }
 
-    template <typename Simd, typename Rng, typename B1, typename B2, typename = typename std::enable_if<is_simd<Simd>::value>::type>
+    template <typename Simd, typename Rng, typename B1, typename B2, typename = std::enable_if_t<is_simd<Simd>::value>>
     void fill_random(Simd& s, Rng& rng, B1 lb, B2 ub) {
         using V = typename Simd::scalar_type;
         constexpr unsigned N = Simd::width;
@@ -65,7 +65,7 @@ namespace {
         s.copy_from(v);
     }
 
-    template <typename Simd, typename Rng, typename = typename std::enable_if<is_simd<Simd>::value>::type>
+    template <typename Simd, typename Rng, typename = std::enable_if_t<is_simd<Simd>::value>>
     void fill_random(Simd& s, Rng& rng) {
         using V = typename Simd::scalar_type;
         constexpr unsigned N = Simd::width;
@@ -1064,7 +1064,8 @@ TYPED_TEST_P(simd_indirect, add_and_subtract) {
 template <typename X>
 bool unique_elements(const X& xs) {
     using std::begin;
-    std::unordered_set<typename std::decay<decltype(*begin(xs))>::type> set;
+// WOOPWOOP
+    std::unordered_set<std::decay_t<decltype(*begin(xs))>> set;
     for (auto& x: xs) {
         if (!set.insert(x).second) return false;
     }
diff --git a/test/unit/test_strprintf.cpp b/test/unit/test_strprintf.cpp
index 3bb01dd8..08cade7d 100644
--- a/test/unit/test_strprintf.cpp
+++ b/test/unit/test_strprintf.cpp
@@ -7,8 +7,8 @@
 #include "../gtest.h"
 #include "common.hpp"
 
+using namespace std::string_literals;
 using namespace arb::util;
-using namespace testing::string_literals;
 
 TEST(strprintf, simple) {
     char buf[200];
@@ -60,6 +60,6 @@ TEST(strprintf, wrappers) {
 
     EXPECT_EQ(std::string(buf), strprintf("sptr %p", sptr));
 
-    EXPECT_EQ("fish"_s, strprintf("fi%s", "sh"_s));
+    EXPECT_EQ("fish"s, strprintf("fi%s", "sh"s));
 }
 
diff --git a/test/unit/test_unique_any.cpp b/test/unit/test_unique_any.cpp
index 038d4341..02612a09 100644
--- a/test/unit/test_unique_any.cpp
+++ b/test/unit/test_unique_any.cpp
@@ -9,7 +9,6 @@
 #include "common.hpp"
 
 using namespace arb;
-using namespace testing::string_literals;
 
 TEST(unique_any, copy_construction) {
     using util::unique_any;
diff --git a/test/unit/test_vector.cpp b/test/unit/test_vector.cpp
index 76f9781b..1176555f 100644
--- a/test/unit/test_vector.cpp
+++ b/test/unit/test_vector.cpp
@@ -31,7 +31,7 @@ TEST(vector, make_view_stdvector) {
 TEST(vector, make_host_stdvector) {
     std::vector<int> stdvec(10);
     auto host_vec = memory::on_host(stdvec);
-    using target_type = std::decay<decltype(host_vec)>::type;
+    using target_type = std::decay_t<decltype(host_vec)>;
     EXPECT_EQ(host_vec.size(), stdvec.size());
     EXPECT_EQ(host_vec.data(), stdvec.data());
     EXPECT_TRUE(memory::util::is_on_host<target_type>());
@@ -44,7 +44,7 @@ TEST(vector, make_host_hostvector) {
     memory::host_vector<int> vec(10);
     {   // test from host_vector
         auto host_view = memory::on_host(vec);
-        using target_type = std::decay<decltype(host_view)>::type;
+        using target_type = std::decay_t<decltype(host_view)>;
         EXPECT_EQ(host_view.size(), vec.size());
         EXPECT_EQ(host_view.data(), vec.data());
         EXPECT_TRUE(memory::util::is_on_host<target_type>());
@@ -54,7 +54,7 @@ TEST(vector, make_host_hostvector) {
     {   // test from view
         auto view = memory::make_view(vec);
         auto host_view = memory::on_host(view);
-        using target_type = std::decay<decltype(host_view)>::type;
+        using target_type = std::decay_t<decltype(host_view)>;
         EXPECT_EQ(host_view.size(), view.size());
         EXPECT_EQ(host_view.data(), view.data());
         EXPECT_TRUE(memory::util::is_on_host<target_type>());
diff --git a/test/unit/test_vector.cu b/test/unit/test_vector.cu
index f3562f02..5a195e48 100644
--- a/test/unit/test_vector.cu
+++ b/test/unit/test_vector.cu
@@ -16,7 +16,7 @@ using namespace arb;
 TEST(vector, make_gpu_stdvector) {
     std::vector<int> stdvec(10);
     auto gpu_vec = memory::on_gpu(stdvec);
-    using target_type = std::decay<decltype(gpu_vec)>::type;
+    using target_type = std::decay_t<decltype(gpu_vec)>;
     EXPECT_EQ(gpu_vec.size(), stdvec.size());
     EXPECT_NE(gpu_vec.data(), stdvec.data());
     EXPECT_TRUE(memory::util::is_on_gpu<target_type>());
@@ -29,7 +29,7 @@ TEST(vector, make_gpu_stdvector) {
 TEST(vector, make_host_devicevector) {
     memory::device_vector<int> dvec(10);
     auto host_vec = memory::on_host(dvec);
-    using target_type = std::decay<decltype(host_vec)>::type;
+    using target_type = std::decay_t<decltype(host_vec)>;
     EXPECT_EQ(host_vec.size(), dvec.size());
     EXPECT_NE(host_vec.data(), dvec.data());
     EXPECT_TRUE(memory::util::is_on_host<target_type>());
@@ -43,7 +43,7 @@ TEST(vector, make_host_devicevector) {
 TEST(vector, make_gpu_devicevector) {
     memory::device_vector<int> dvec(10);
     auto view = memory::on_gpu(dvec);
-    using target_type = std::decay<decltype(view)>::type;
+    using target_type = std::decay_t<decltype(view)>;
     EXPECT_EQ(view.size(), dvec.size());
     EXPECT_EQ(view.data(), dvec.data());
     EXPECT_TRUE(memory::util::is_on_gpu<target_type>());
diff --git a/test/validation/trace_analysis.hpp b/test/validation/trace_analysis.hpp
index 167952cb..e6ff032d 100644
--- a/test/validation/trace_analysis.hpp
+++ b/test/validation/trace_analysis.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cfloat>
 #include <vector>
 
 #include "../gtest.h"
@@ -7,9 +8,6 @@
 #include <arbor/simple_sampler.hpp>
 #include <arbor/util/optional.hpp>
 
-#include "math.hpp"
-#include "util/path.hpp"
-#include "util/deduce_return.hpp"
 #include "util/rangeutil.hpp"
 
 namespace arb {
@@ -18,26 +16,15 @@ namespace arb {
 
 // Extract time or value data from trace.
 
-namespace impl {
-    // NB: work-around for lack of function return type deduction
-    // in C++11; can't use lambda within DEDUCED_RETURN_TYPE.
-
-    template <typename V>
-    inline float time(const trace_entry<V>& x) { return x.t; }
-
-    template <typename V>
-    inline const V& value(const trace_entry<V>& x) { return x.v; }
-}
-
 template <typename V>
-inline auto times(const trace_data<V>& trace) DEDUCED_RETURN_TYPE(
-   util::transform_view(trace, impl::time<V>)
-)
+inline auto times(const trace_data<V>& trace) {
+   return util::transform_view(trace, [](auto& x) { return x.t; });
+}
 
 template <typename V>
-inline auto values(const trace_data<V>& trace) DEDUCED_RETURN_TYPE(
-   util::transform_view(trace, impl::value<V>)
-)
+inline auto values(const trace_data<V>& trace) {
+   return util::transform_view(trace, [](auto& x) { return x.v; });
+}
 
 // Compute max |v_i - f(t_i)| where (t, v) is the 
 // first trace `u` and f is the piece-wise linear interpolant
@@ -93,7 +80,7 @@ void assert_convergence(const ConvEntrySeq& cs) {
     if (util::empty(cs)) return;
 
     auto tbound = [](trace_peak p) { return std::abs(p.t)+p.t_err; };
-    float peak_dt_bound = math::infinity<>();
+    float peak_dt_bound = INFINITY;
 
     for (auto pi = std::begin(cs); std::next(pi)!=std::end(cs); ++pi) {
         const auto& p = *pi;
diff --git a/test/validation/validate_kinetic.cpp b/test/validation/validate_kinetic.cpp
index a1d78a56..dd335976 100644
--- a/test/validation/validate_kinetic.cpp
+++ b/test/validation/validate_kinetic.cpp
@@ -37,7 +37,8 @@ void run_kinetic_dt(
 
     cable1d_recipe rec{c};
     rec.add_probe(0, 0, probe);
-    probe_label plabels[1] = {"soma.mid", {0u, 0u}};
+
+    probe_label plabels[1] = {{"soma.mid", {0u, 0u}}};
 
     meta["sim"] = "arbor";
     meta["backend_kind"] = util::to_string(backend);
diff --git a/test/validation/validate_soma.cpp b/test/validation/validate_soma.cpp
index 27458680..90534c4c 100644
--- a/test/validation/validate_soma.cpp
+++ b/test/validation/validate_soma.cpp
@@ -30,7 +30,7 @@ void validate_soma(backend_kind backend) {
 
     cable1d_recipe rec{c};
     rec.add_probe(0, 0, cell_probe_address{{0, 0.5}, cell_probe_address::membrane_voltage});
-    probe_label plabels[1] = {"soma.mid", {0u, 0u}};
+    probe_label plabels[1] = {{"soma.mid", {0u, 0u}}};
 
     distributed_context context;
     hw::node_info nd(1, backend==backend_kind::gpu? 1: 0);
-- 
GitLab