public
Last active

Construct Chainable -- A demonstration of a left-associative type constructor.

  • Download Gist
constructChainable.cpp
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
#include <utility>
#include <iostream>
 
// http://en.cppreference.com/w/cpp/types/decay
template< class X >
using Decay = typename std::decay<X>::type;
 
// ConstructBinary<T>(X,Y) = T<X,Y>
template< template<class...> class X >
struct ConstructBinary {
// Construct(const X&,const Y&) = T<X,Y>
template< class Y, class Z, class R = X< Decay<Y>, Decay<Z> > >
constexpr R operator () ( Y&& y, Z&& z ) {
return R( std::forward<Y>(y), std::forward<Z>(z) );
}
};
 
// Addition is an example of an associative operation.
// x + y + z = (z+y) + z
// If we let (+) be an operation that creates a pair, then
// x (+) y = (x,y)
// And if we consider this operation left associative (meaning from left to right),
// x (+) y (+) z = (z,y) (+) z = ((z,y),z)
// If we think of this as working on some type, T,
// X (+) Y (+) Z = T(X,Y) (+) Z = T( T(X,Y), Z )
template< template<class...> class X >
struct ConstructChainable : ConstructBinary<X> {
using Self = ConstructChainable<X>;
using ConstructBinary<X>::operator();
 
template< class Y, class Z, class A, class ...B,
class R1 = X< Decay<Y>, Decay<Z> >,
class R2 = decltype( Self()( std::declval<R1>(), std::declval<A>(),
std::declval<B>()... ) ) >
constexpr R2 operator () ( Y&& y, Z&& z, A&& a, B&& ...b ) {
return (*this) (
R1( std::forward<Y>(y), std::forward<Z>(z) ),
std::forward<A>(a), std::forward<B>(b)...
);
}
};
 
 
constexpr auto make_pair = ConstructBinary< std::pair >();
constexpr auto make_tuple = ConstructChainable< std::pair >();
 
template< class X, class Y >
std::ostream& operator << ( std::ostream& os, const std::pair<X,Y>& p ) {
os << '(' << p.first << ',' << p.second << ')';
return os;
}
 
int main() {
std::cout << make_pair(1,2) << std::endl; // p(1,2)
std::cout << make_tuple(1,2,3,4,5) << std::endl; // p( p( p( p(1,2), 3 ), 4 ) 5 )
}

The output is (((((1,2),3),4),5)

What's all the Decay stuff for?

The idea is to perfect forward the arguments, but the container is supposed to contain the values. So, if passed in a int&, I want the pair to hold an int. Of coarse, you could also write a version that forwarded the type, too.

Oh, and if you're not familiar with std::decay, it's basically like remove_reference, remove_const, and a couple other things, all rolled in to one.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.