From 0cf6fb50632111bd85e2e8e4ccce1ccf0d42bffb Mon Sep 17 00:00:00 2001
From: Jonathan Gopel <jgopel@gmail.com>
Date: Sun, 5 Jun 2022 14:55:05 +0000
Subject: [PATCH 1/4] Add constexpr to transform_inclusive_scan()

Problem:
- C++20 added `constexpr` to `transform_inclusive_scan()`. The existing
  implementations could be `constexpr` but were not updated.

Solution:
- Add `constexpr`.
---
 .../cxx17/transform_inclusive_scan.hpp        |  2 +
 test/transform_inclusive_scan_test.cpp        | 46 ++++++++++++++++++-
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp b/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
index 9d877c02d..2b463e960 100644
--- a/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
+++ b/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
@@ -39,6 +39,7 @@ namespace boost { namespace algorithm {
 /// \note This function is part of the C++17 standard library
 template<class InputIterator, class OutputIterator,
          class BinaryOperation, class UnaryOperation, class T>
+BOOST_CXX14_CONSTEXPR
 OutputIterator transform_inclusive_scan(InputIterator first, InputIterator last,
                                         OutputIterator result,
                                         BinaryOperation bOp, UnaryOperation uOp,
@@ -68,6 +69,7 @@ OutputIterator transform_inclusive_scan(InputIterator first, InputIterator last,
 /// \note This function is part of the C++17 standard library
 template<class InputIterator, class OutputIterator,
          class BinaryOperation, class UnaryOperation>
+BOOST_CXX14_CONSTEXPR
 OutputIterator transform_inclusive_scan(InputIterator first, InputIterator last,
                                         OutputIterator result,
                                         BinaryOperation bOp, UnaryOperation uOp)
diff --git a/test/transform_inclusive_scan_test.cpp b/test/transform_inclusive_scan_test.cpp
index 1ce01c638..980c1a4d6 100644
--- a/test/transform_inclusive_scan_test.cpp
+++ b/test/transform_inclusive_scan_test.cpp
@@ -27,7 +27,7 @@ int triangle(int n) { return n*(n+1)/2; }
 template <class _Tp>
 struct identity
 {
-    const _Tp& operator()(const _Tp& __x) const { return __x;}
+    BOOST_CXX14_CONSTEXPR const _Tp& operator()(const _Tp& __x) const { return __x;}
 };
 
 
@@ -108,6 +108,23 @@ void basic_transform_inclusive_scan_tests()
     }
 }
 
+BOOST_CXX14_CONSTEXPR bool constexpr_transform_inclusive_scan_tests() {
+    typedef random_access_iterator<int*> iterator_t;
+
+    const int NUM_ELEMENTS = 3;
+
+    bool status = true;
+    int input[NUM_ELEMENTS] = {3, 3, 3};
+    int output[NUM_ELEMENTS] = {0, 0, 0};
+    ba::transform_inclusive_scan(
+        iterator_t(input), iterator_t(input + NUM_ELEMENTS),
+        iterator_t(output),
+        std::plus<int>(), identity<int>());
+    for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+        status &= (output[i] == input[0] + (int)(i * 3));
+    return status;
+}
+
 void test_transform_inclusive_scan()
 {
     basic_transform_inclusive_scan_tests();
@@ -119,6 +136,11 @@ void test_transform_inclusive_scan()
     transform_inclusive_scan_test<random_access_iterator<const int*> >();
     transform_inclusive_scan_test<const int*>();
     transform_inclusive_scan_test<      int*>();
+
+    {
+        BOOST_CXX14_CONSTEXPR bool status = constexpr_transform_inclusive_scan_tests();
+        BOOST_CHECK(status == true);
+    }
 }
 
 
@@ -211,6 +233,24 @@ void basic_transform_inclusive_scan_init_tests()
     }
 }
 
+BOOST_CXX14_CONSTEXPR bool constexpr_transform_inclusive_scan_init_tests() {
+    typedef random_access_iterator<int*> iterator_t;
+
+    const int NUM_ELEMENTS = 3;
+
+    bool status = true;
+    int input[NUM_ELEMENTS] = {3, 3, 3};
+    int output[NUM_ELEMENTS] = {0, 0, 0};
+    ba::transform_inclusive_scan(
+        iterator_t(input), iterator_t(input + NUM_ELEMENTS),
+        iterator_t(output),
+        std::plus<int>(), identity<int>(),
+        30);
+    for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+        status &= (output[i] == 30 + (int)((i + 1) * 3));
+    return status;
+}
+
 void test_transform_inclusive_scan_init()
 {
 	basic_transform_inclusive_scan_init_tests();
@@ -223,6 +263,10 @@ void test_transform_inclusive_scan_init()
     transform_inclusive_scan_init_test<const int*>();
     transform_inclusive_scan_init_test<      int*>();
 
+    {
+        BOOST_CXX14_CONSTEXPR bool status = constexpr_transform_inclusive_scan_init_tests();
+        BOOST_CHECK(status == true);
+    }
 }
 
 

From 7a9d9dffdf3f5ad3102e157ca18350449fd2633b Mon Sep 17 00:00:00 2001
From: Jonathan Gopel <jgopel@gmail.com>
Date: Sun, 5 Jun 2022 14:55:26 +0000
Subject: [PATCH 2/4] Add dual-sequence implementation of
 transform_inclusive_scan()

Problem:
- `transform()` has an overload that takes two input sequences - in
  other languages this is called `zip()`.
- `transform_reduce()` has an overload that takes two input sequences.
- `transform_inclusive_scan()` does not have this overload, despite its
  obvious utility from the other cases.

Solution:
- Implement an overload of `transform_inclusive_scan()` that takes two
  input sequences.
---
 .../cxx17/transform_inclusive_scan.hpp        |  29 ++++
 test/transform_inclusive_scan_test.cpp        | 139 +++++++++++++++---
 2 files changed, 148 insertions(+), 20 deletions(-)

diff --git a/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp b/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
index 2b463e960..cc468cfad 100644
--- a/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
+++ b/include/boost/algorithm/cxx17/transform_inclusive_scan.hpp
@@ -85,6 +85,35 @@ OutputIterator transform_inclusive_scan(InputIterator first, InputIterator last,
     return result;
 }
 
+/// \fn transform_inclusive_scan ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, ScanOperation scan_op, TransformOperation trans_op, T init )
+/// \brief Transforms elements from the input ranges with trans_op and then
+/// combines those transformed elements with scan_op such that the n-1th element
+/// and the nth element are combined. Inclusivity means that the nth element is
+/// included in the nth combination. The first value will be used as the init.
+/// \return The updated output iterator
+///
+/// \param first1   The start of the first input sequence
+/// \param last1    The end of the first input sequence
+/// \param first2   The start of the second input sequence
+/// \param result   The output iterator to write the results into
+/// \param scan_op  The operation for combining transformed input elements
+/// \param trans_op The operation for transforming pairs of input elements
+/// \param init     The initial value
+template<class InputIterator1, class InputIterator2, class OutputIterator,
+         class ScanOperation, class TransformOperation, class T>
+BOOST_CXX14_CONSTEXPR
+OutputIterator transform_inclusive_scan(InputIterator1 first1, InputIterator1 last1,
+                                        InputIterator2 first2,
+                                        OutputIterator result,
+                                        ScanOperation scan_op, TransformOperation trans_op,
+                                        T init)
+{
+    for (; first1 != last1; ++first1, ++first2, ++result) {
+        init = scan_op(init, trans_op(*first1, *first2));
+        *result = init;
+        }
+    return result;
+}
 
 }} // namespace boost and algorithm
 
diff --git a/test/transform_inclusive_scan_test.cpp b/test/transform_inclusive_scan_test.cpp
index 980c1a4d6..133907b97 100644
--- a/test/transform_inclusive_scan_test.cpp
+++ b/test/transform_inclusive_scan_test.cpp
@@ -10,6 +10,8 @@
 #include <vector>
 #include <functional>
 #include <numeric>
+#include <iterator>
+#include <cstddef>
 
 #include <boost/config.hpp>
 #include <boost/algorithm/cxx11/iota.hpp>
@@ -146,7 +148,7 @@ void test_transform_inclusive_scan()
 
 template <class Iter1, class BOp, class UOp, class T, class Iter2>
 void
-transform_inclusive_scan_init_test(Iter1 first, Iter1 last, BOp bop, UOp uop, T init, Iter2 rFirst, Iter2 rLast)
+transform_inclusive_scan_init_single_input_test(Iter1 first, Iter1 last, BOp bop, UOp uop, T init, Iter2 rFirst, Iter2 rLast)
 {
     std::vector<typename std::iterator_traits<Iter1>::value_type> v;
 //  Test not in-place
@@ -162,12 +164,42 @@ transform_inclusive_scan_init_test(Iter1 first, Iter1 last, BOp bop, UOp uop, T
     BOOST_CHECK(std::equal(v.begin(), v.end(), rFirst));
 }
 
+template <class Iter1, class Iter2, class ScanOperation, class TransformOperation, class T, class Iter3>
+void
+transform_inclusive_scan_init_dual_input_test(Iter1 first1, Iter1 last1,
+                                              Iter2 first2,
+                                              ScanOperation scan_op, TransformOperation trans_op,
+                                              T init, Iter3 expected_first, Iter3 expected_last)
+{
+    { // Test not in-place
+        std::vector<typename std::iterator_traits<Iter3>::value_type> output;
+        ba::transform_inclusive_scan(first1, last1, first2, std::back_inserter(output), scan_op, trans_op, init);
+        const typename std::iterator_traits<Iter3>::difference_type result_size = std::distance(expected_first, expected_last);
+        BOOST_CHECK(result_size >= 0);
+        BOOST_CHECK(static_cast<std::size_t>(result_size) == output.size());
+        BOOST_CHECK(std::equal(output.begin(), output.end(), expected_first));
+    }
+    { // Test in-place
+        std::vector<typename std::iterator_traits<Iter3>::value_type> v(first1, last1);
+        ba::transform_inclusive_scan(v.begin(), v.end(), first2,  v.begin(), scan_op, trans_op, init);
+        const typename std::iterator_traits<Iter3>::difference_type result_size = std::distance(expected_first, expected_last);
+        BOOST_CHECK(result_size >= 0);
+        BOOST_CHECK(static_cast<std::size_t>(result_size) == v.size());
+        BOOST_CHECK(std::equal(v.begin(), v.end(), expected_first));
+    }
+}
 
 template <class Iter>
 void
 transform_inclusive_scan_init_test()
 {
-          int ia[]     = {  1,  3,   5,    7,     9};
+    int ia1[]    = { 1, 3, 5, 7,  9};
+    int ia2[]    = { 2, 4, 6, 8, 10};
+
+    const unsigned sa = sizeof(ia1) / sizeof(ia1[0]);
+    BOOST_CHECK(sa == sizeof(ia2) / sizeof(ia2[0]));  // just to be sure
+
+    // single input results
     const int pResI0[] = {  1,  4,   9,   16,    25};        // with identity
     const int mResI0[] = {  0,  0,   0,    0,     0};
     const int pResN0[] = { -1, -4,  -9,  -16,   -25};        // with negate
@@ -176,7 +208,7 @@ transform_inclusive_scan_init_test()
     const int mResI2[] = {  2,  6,  30,  210,  1890};
     const int pResN2[] = {  1, -2,  -7,  -14,   -23};        // with negate
     const int mResN2[] = { -2,  6, -30,  210, -1890};
-    const unsigned sa = sizeof(ia) / sizeof(ia[0]);
+
     BOOST_CHECK(sa == sizeof(pResI0) / sizeof(pResI0[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(mResI0) / sizeof(mResI0[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(pResN0) / sizeof(pResN0[0]));       // just to be sure
@@ -186,15 +218,67 @@ transform_inclusive_scan_init_test()
     BOOST_CHECK(sa == sizeof(pResN2) / sizeof(pResN2[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(mResN2) / sizeof(mResN2[0]));       // just to be sure
 
+    // dual input results
+    const int pResP0[] = {  3,  10,  21,  36,  55};
+    const int pResP2[] = {  5,  12,  23,  38,  57};
+    const int pResM0[] = { -1,  -2,  -3,  -4,  -5};
+    const int pResM2[] = {  1,   0,  -1,  -2,  -3};
+    const int mResP0[] = { -3, -10, -21, -36, -55};
+    const int mResP2[] = { -1,  -8, -19, -34, -53};
+    const int mResM0[] = {  1,   2,   3,   4,   5};
+    const int mResM2[] = {  3,   4,   5,   6,   7};
+
+    BOOST_CHECK(sa == sizeof(pResP0) / sizeof(pResP0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResP2) / sizeof(pResP2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResM0) / sizeof(pResM0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResM2) / sizeof(pResM2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResP0) / sizeof(mResP0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResP2) / sizeof(mResP2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResM0) / sizeof(mResM0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResM2) / sizeof(mResM2[0]));  // just to be sure
+
     for (unsigned int i = 0; i < sa; ++i ) {
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::plus<int>(),       identity<int>(),    0, pResI0, pResI0 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::multiplies<int>(), identity<int>(),    0, mResI0, mResI0 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::plus<int>(),       std::negate<int>(), 0, pResN0, pResN0 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::multiplies<int>(), std::negate<int>(), 0, mResN0, mResN0 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::plus<int>(),       identity<int>(),    2, pResI2, pResI2 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::multiplies<int>(), identity<int>(),    2, mResI2, mResI2 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::plus<int>(),       std::negate<int>(), 2, pResN2, pResN2 + i);
-        transform_inclusive_scan_init_test(Iter(ia), Iter(ia + i), std::multiplies<int>(), std::negate<int>(), 2, mResN2, mResN2 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       identity<int>(),    0, pResI0, pResI0 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), identity<int>(),    0, mResI0, mResI0 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       std::negate<int>(), 0, pResN0, pResN0 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), std::negate<int>(), 0, mResN0, mResN0 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       identity<int>(),    2, pResI2, pResI2 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), identity<int>(),    2, mResI2, mResI2 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       std::negate<int>(), 2, pResN2, pResN2 + i);
+        transform_inclusive_scan_init_single_input_test(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), std::negate<int>(), 2, mResN2, mResN2 + i);
+
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::plus<int>(), std::plus<int>(), 0,
+                                                      pResP0, pResP0 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::plus<int>(), std::plus<int>(), 2,
+                                                      pResP2, pResP2 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::plus<int>(), std::minus<int>(), 0,
+                                                      pResM0, pResM0 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::plus<int>(), std::minus<int>(), 2,
+                                                      pResM2, pResM2 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::minus<int>(), std::plus<int>(), 0,
+                                                      mResP0, mResP0 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::minus<int>(), std::plus<int>(), 2,
+                                                      mResP2, mResP2 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::minus<int>(), std::minus<int>(), 0,
+                                                      mResM0, mResM0 + i);
+        transform_inclusive_scan_init_dual_input_test(Iter(ia1), Iter(ia1 + i),
+                                                      Iter(ia2),
+                                                      std::minus<int>(), std::minus<int>(), 2,
+                                                      mResM2, mResM2 + i);
         }
 }
 
@@ -239,15 +323,30 @@ BOOST_CXX14_CONSTEXPR bool constexpr_transform_inclusive_scan_init_tests() {
     const int NUM_ELEMENTS = 3;
 
     bool status = true;
-    int input[NUM_ELEMENTS] = {3, 3, 3};
-    int output[NUM_ELEMENTS] = {0, 0, 0};
-    ba::transform_inclusive_scan(
-        iterator_t(input), iterator_t(input + NUM_ELEMENTS),
-        iterator_t(output),
-        std::plus<int>(), identity<int>(),
-        30);
-    for (size_t i = 0; i < NUM_ELEMENTS; ++i)
-        status &= (output[i] == 30 + (int)((i + 1) * 3));
+    { // Single input range
+        int input[NUM_ELEMENTS] = {3, 3, 3};
+        int output[NUM_ELEMENTS] = {0, 0, 0};
+        ba::transform_inclusive_scan(
+            iterator_t(input), iterator_t(input + NUM_ELEMENTS),
+            iterator_t(output),
+            std::plus<int>(), identity<int>(),
+            30);
+        for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+            status &= (output[i] == 30 + (int)((i + 1) * 3));
+    }
+    { // Dual input ranges
+        int input1[NUM_ELEMENTS] = {3, 3, 3};
+        int input2[NUM_ELEMENTS] = {1, 1, 1};
+        int output[NUM_ELEMENTS] = {0, 0, 0};
+        ba::transform_inclusive_scan(
+            iterator_t(input1), iterator_t(input1 + NUM_ELEMENTS),
+            iterator_t(input2),
+            iterator_t(output),
+            std::plus<int>(), std::minus<int>(),
+            30);
+        for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+            status &= (output[i] == 30 + (int)((i + 1) * 2));
+    }
     return status;
 }
 

From fa6e930a14a565bdb3c44aadeee3c419baad1134 Mon Sep 17 00:00:00 2001
From: Jonathan Gopel <jgopel@gmail.com>
Date: Sun, 5 Jun 2022 23:23:42 +0000
Subject: [PATCH 3/4] Add constexpr to transform_exclusive_scan()

Problem:
- C++20 added `constexpr` to `transform_exclusive_scan()`. The existing
  implementation could be `constexpr` but was not updated.

Solution:
- Add `constexpr`.
---
 .../cxx17/transform_exclusive_scan.hpp        |  1 +
 test/transform_exclusive_scan_test.cpp        | 24 ++++++++++++++++++-
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp b/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
index 86446b803..a383b7e4f 100644
--- a/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
+++ b/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
@@ -39,6 +39,7 @@ namespace boost { namespace algorithm {
 /// \note This function is part of the C++17 standard library
 template<class InputIterator, class OutputIterator, class T,
          class BinaryOperation, class UnaryOperation>
+BOOST_CXX14_CONSTEXPR
 OutputIterator transform_exclusive_scan(InputIterator first, InputIterator last,
                                         OutputIterator result, T init,
                                         BinaryOperation bOp, UnaryOperation uOp)
diff --git a/test/transform_exclusive_scan_test.cpp b/test/transform_exclusive_scan_test.cpp
index 6259f2bc3..fbb24953b 100644
--- a/test/transform_exclusive_scan_test.cpp
+++ b/test/transform_exclusive_scan_test.cpp
@@ -27,7 +27,7 @@ int triangle(int n) { return n*(n+1)/2; }
 template <class _Tp>
 struct identity
 {
-    const _Tp& operator()(const _Tp& __x) const { return __x;}
+    BOOST_CXX14_CONSTEXPR const _Tp& operator()(const _Tp& __x) const { return __x;}
 };
 
 
@@ -118,6 +118,23 @@ void basic_tests()
     }
 }
 
+BOOST_CXX14_CONSTEXPR bool constexpr_transform_exclusive_scan_tests() {
+    typedef random_access_iterator<int*> iterator_t;
+
+    const int NUM_ELEMENTS = 3;
+
+    bool status = true;
+    int input[NUM_ELEMENTS] = {3, 3, 3};
+    int output[NUM_ELEMENTS] = {0, 0, 0};
+    ba::transform_exclusive_scan(
+        iterator_t(input), iterator_t(input + NUM_ELEMENTS),
+        iterator_t(output),
+        30,
+        std::plus<int>(), identity<int>());
+    for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+        status &= (output[i] == 30 + (int)(i * 3));
+    return status;
+}
 
 void test_transform_exclusive_scan_init()
 {
@@ -129,6 +146,11 @@ void test_transform_exclusive_scan_init()
     test<random_access_iterator<const int*> >();
     test<const int*>();
     test<      int*>();
+
+    {
+        BOOST_CXX14_CONSTEXPR bool status = constexpr_transform_exclusive_scan_tests();
+        BOOST_CHECK(status == true);
+    }
 }
 
 BOOST_AUTO_TEST_CASE( test_main )

From 2c5d484358604a7ceaef1bb1387b5c2a570c0dcf Mon Sep 17 00:00:00 2001
From: Jonathan Gopel <jgopel@gmail.com>
Date: Sun, 5 Jun 2022 23:53:58 +0000
Subject: [PATCH 4/4] Add dual-sequence implementation of
 transform_exclusive_scan()

Problem:
- `transform()` has an overload that takes two input sequences - in
  other languages this is called `zip()`.
- `transform_reduce()` has an overload that takes two input sequences.
- `transform_inclusive_scan()` has an overload that takes two input
  sequences.
- `transform_exclusive_scan()` does not have this overload, despite its
  obvious utility from the other cases.

Solution:
- Implement an overload of `transform_exclusive_scan()` that takes two
  input ranges.
---
 .../cxx17/transform_exclusive_scan.hpp        |  37 ++++++
 test/transform_exclusive_scan_test.cpp        | 115 +++++++++++++++---
 2 files changed, 132 insertions(+), 20 deletions(-)

diff --git a/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp b/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
index a383b7e4f..f1957d65a 100644
--- a/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
+++ b/include/boost/algorithm/cxx17/transform_exclusive_scan.hpp
@@ -58,6 +58,43 @@ OutputIterator transform_exclusive_scan(InputIterator first, InputIterator last,
     return result;
 }
 
+/// \fn transform_exclusive_scan ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, ScanOperation scan_op, TransformOperation trans_op, T init )
+/// \brief Transforms elements from the input range with uOp and then combines
+/// those transformed elements with bOp such that the n-1th element and the nth
+/// element are combined. Exclusivity means that the nth element is not
+/// included in the nth combination.
+/// \return The updated output iterator
+///
+/// \param first1   The start of the first input sequence
+/// \param last1    The end of the first input sequence
+/// \param first2   The start of the second input sequence
+/// \param result   The output iterator to write the results into
+/// \param scan_op  The operation for combining transformed input elements
+/// \param trans_op The operation for transforming pairs of input elements
+/// \param init     The initial value
+template<class InputIterator1, class InputIterator2, class OutputIterator,
+         class ScanOperation, class TransformOperation, class T>
+BOOST_CXX14_CONSTEXPR
+OutputIterator transform_exclusive_scan(InputIterator1 first1, InputIterator1 last1,
+                                        InputIterator2 first2,
+                                        OutputIterator result, T init,
+                                        ScanOperation scan_op, TransformOperation trans_op)
+{
+    if (first1 != last1)
+    {
+        T saved = init;
+        do
+        {
+            init = scan_op(init, trans_op(*first1, *first2));
+            *result = saved;
+            saved = init;
+            ++first2;
+            ++result;
+        } while (++first1 != last1);
+    }
+    return result;
+}
+
 }} // namespace boost and algorithm
 
 #endif // BOOST_ALGORITHM_TRANSFORM_EXCLUSIVE_SCAN_HPP
diff --git a/test/transform_exclusive_scan_test.cpp b/test/transform_exclusive_scan_test.cpp
index fbb24953b..a08046c83 100644
--- a/test/transform_exclusive_scan_test.cpp
+++ b/test/transform_exclusive_scan_test.cpp
@@ -10,6 +10,8 @@
 #include <vector>
 #include <functional>
 #include <numeric>
+#include <algorithm>
+#include <iterator>
 
 #include <boost/config.hpp>
 #include <boost/algorithm/cxx11/iota.hpp>
@@ -33,7 +35,7 @@ struct identity
 
 template <class Iter1, class BOp, class UOp, class T, class Iter2>
 void
-test(Iter1 first, Iter1 last, BOp bop, UOp uop, T init, Iter2 rFirst, Iter2 rLast)
+test_single_input(Iter1 first, Iter1 last, BOp bop, UOp uop, T init, Iter2 rFirst, Iter2 rLast)
 {
     std::vector<typename std::iterator_traits<Iter1>::value_type> v;
 //  Test not in-place
@@ -49,12 +51,42 @@ test(Iter1 first, Iter1 last, BOp bop, UOp uop, T init, Iter2 rFirst, Iter2 rLas
     BOOST_CHECK(std::equal(v.begin(), v.end(), rFirst));
 }
 
+template <class Iter1, class Iter2, class ScanOperation, class TransformOperation, class T, class Iter3>
+void
+test_dual_input(Iter1 first1, Iter1 last1,
+                Iter2 first2,
+                ScanOperation scan_op, TransformOperation trans_op,
+                T init, Iter3 expected_first, Iter3 expected_last)
+{
+    { // Test not in-place
+        std::vector<typename std::iterator_traits<Iter3>::value_type> output;
+        ba::transform_exclusive_scan(first1, last1, first2, std::back_inserter(output), init, scan_op, trans_op);
+        const typename std::iterator_traits<Iter3>::difference_type result_size = std::distance(expected_first, expected_last);
+        BOOST_CHECK(result_size >= 0);
+        BOOST_CHECK(static_cast<std::size_t>(result_size) == output.size());
+        BOOST_CHECK(std::equal(output.begin(), output.end(), expected_first));
+    }
+    { // Test in-place
+        std::vector<typename std::iterator_traits<Iter3>::value_type> v(first1, last1);
+        ba::transform_exclusive_scan(v.begin(), v.end(), first2, v.begin(), init, scan_op, trans_op);
+        const typename std::iterator_traits<Iter3>::difference_type result_size = std::distance(expected_first, expected_last);
+        BOOST_CHECK(result_size >= 0);
+        BOOST_CHECK(static_cast<std::size_t>(result_size) == v.size());
+        BOOST_CHECK(std::equal(v.begin(), v.end(), expected_first));
+    }
+}
 
 template <class Iter>
 void
 test()
 {
-          int ia[]     = { 1,  3,  5,   7,   9};
+    int ia1[] = { 1, 3, 5, 7,  9};
+    int ia2[] = { 2, 4, 6, 8, 10};
+
+    const unsigned sa = sizeof(ia1) / sizeof(ia1[0]);
+    BOOST_CHECK(sa == sizeof(ia2) / sizeof(ia2[0]));  // just to be sure
+
+    // single input results
     const int pResI0[] = { 0,  1,  4,   9,  16};        // with identity
     const int mResI0[] = { 0,  0,  0,   0,   0};
     const int pResN0[] = { 0, -1, -4,  -9, -16};        // with negate
@@ -63,7 +95,7 @@ test()
     const int mResI2[] = { 2,  2,  6,  30, 210};
     const int pResN2[] = { 2,  1, -2,  -7, -14};        // with negate
     const int mResN2[] = { 2, -2,  6, -30, 210};
-    const unsigned sa = sizeof(ia) / sizeof(ia[0]);
+
     BOOST_CHECK(sa == sizeof(pResI0) / sizeof(pResI0[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(mResI0) / sizeof(mResI0[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(pResN0) / sizeof(pResN0[0]));       // just to be sure
@@ -73,15 +105,43 @@ test()
     BOOST_CHECK(sa == sizeof(pResN2) / sizeof(pResN2[0]));       // just to be sure
     BOOST_CHECK(sa == sizeof(mResN2) / sizeof(mResN2[0]));       // just to be sure
 
+    // dual input results
+    const int pResP0[] = {  0,   3,  10,  21,  36};
+    const int pResP2[] = {  2,   5,  12,  23,  38};
+    const int pResM0[] = {  0,  -1,  -2,  -3,  -4};
+    const int pResM2[] = {  2,   1,   0,  -1,  -2};
+    const int mResP0[] = {  0,  -3, -10, -21, -36};
+    const int mResP2[] = {  2,  -1,  -8, -19, -34};
+    const int mResM0[] = {  0,   1,   2,   3,   4};
+    const int mResM2[] = {  2,   3,   4,   5,   6};
+
+    BOOST_CHECK(sa == sizeof(pResP0) / sizeof(pResP0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResP2) / sizeof(pResP2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResM0) / sizeof(pResM0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(pResM2) / sizeof(pResM2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResP0) / sizeof(mResP0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResP2) / sizeof(mResP2[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResM0) / sizeof(mResM0[0]));  // just to be sure
+    BOOST_CHECK(sa == sizeof(mResM2) / sizeof(mResM2[0]));  // just to be sure
+
     for (unsigned int i = 0; i < sa; ++i ) {
-        test(Iter(ia), Iter(ia + i), std::plus<int>(),       identity<int>(),    0, pResI0, pResI0 + i);
-        test(Iter(ia), Iter(ia + i), std::multiplies<int>(), identity<int>(),    0, mResI0, mResI0 + i);
-        test(Iter(ia), Iter(ia + i), std::plus<int>(),       std::negate<int>(), 0, pResN0, pResN0 + i);
-        test(Iter(ia), Iter(ia + i), std::multiplies<int>(), std::negate<int>(), 0, mResN0, mResN0 + i);
-        test(Iter(ia), Iter(ia + i), std::plus<int>(),       identity<int>(),    2, pResI2, pResI2 + i);
-        test(Iter(ia), Iter(ia + i), std::multiplies<int>(), identity<int>(),    2, mResI2, mResI2 + i);
-        test(Iter(ia), Iter(ia + i), std::plus<int>(),       std::negate<int>(), 2, pResN2, pResN2 + i);
-        test(Iter(ia), Iter(ia + i), std::multiplies<int>(), std::negate<int>(), 2, mResN2, mResN2 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       identity<int>(),    0, pResI0, pResI0 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), identity<int>(),    0, mResI0, mResI0 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       std::negate<int>(), 0, pResN0, pResN0 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), std::negate<int>(), 0, mResN0, mResN0 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       identity<int>(),    2, pResI2, pResI2 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), identity<int>(),    2, mResI2, mResI2 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::plus<int>(),       std::negate<int>(), 2, pResN2, pResN2 + i);
+        test_single_input(Iter(ia1), Iter(ia1 + i), std::multiplies<int>(), std::negate<int>(), 2, mResN2, mResN2 + i);
+
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::plus<int>(), std::plus<int>(), 0, pResP0, pResP0 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::plus<int>(), std::plus<int>(), 2, pResP2, pResP2 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::plus<int>(), std::minus<int>(), 0, pResM0, pResM0 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::plus<int>(), std::minus<int>(), 2, pResM2, pResM2 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::minus<int>(), std::plus<int>(), 0, mResP0, mResP0 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::minus<int>(), std::plus<int>(), 2, mResP2, mResP2 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::minus<int>(), std::minus<int>(), 0, mResM0, mResM0 + i);
+        test_dual_input(Iter(ia1), Iter(ia1 + i), Iter(ia2), std::minus<int>(), std::minus<int>(), 2, mResM2, mResM2 + i);
         }
 }
 
@@ -124,15 +184,30 @@ BOOST_CXX14_CONSTEXPR bool constexpr_transform_exclusive_scan_tests() {
     const int NUM_ELEMENTS = 3;
 
     bool status = true;
-    int input[NUM_ELEMENTS] = {3, 3, 3};
-    int output[NUM_ELEMENTS] = {0, 0, 0};
-    ba::transform_exclusive_scan(
-        iterator_t(input), iterator_t(input + NUM_ELEMENTS),
-        iterator_t(output),
-        30,
-        std::plus<int>(), identity<int>());
-    for (size_t i = 0; i < NUM_ELEMENTS; ++i)
-        status &= (output[i] == 30 + (int)(i * 3));
+    { // Single input range
+        int input[NUM_ELEMENTS] = {3, 3, 3};
+        int output[NUM_ELEMENTS] = {0, 0, 0};
+        ba::transform_exclusive_scan(
+            iterator_t(input), iterator_t(input + NUM_ELEMENTS),
+            iterator_t(output),
+            30,
+            std::plus<int>(), identity<int>());
+        for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+            status &= (output[i] == 30 + (int)(i * 3));
+    }
+    { // Dual input ranges
+        int input1[NUM_ELEMENTS] = {3, 3, 3};
+        int input2[NUM_ELEMENTS] = {1, 1, 1};
+        int output[NUM_ELEMENTS] = {0, 0, 0};
+        ba::transform_exclusive_scan(
+            iterator_t(input1), iterator_t(input1 + NUM_ELEMENTS),
+            iterator_t(input2),
+            iterator_t(output),
+            30,
+            std::plus<int>(), std::minus<int>());
+        for (size_t i = 0; i < NUM_ELEMENTS; ++i)
+            status &= (output[i] == 30 + (int)(i * 2));
+    }
     return status;
 }