I have an implementation that is slightly different from what you requested, but maybe this will work for you. I created a structure that looks like a list, which when you try to add an element of a new type to it, either copies or moves to an envelope container (of another type) that may contain this new type of element. (As a permanent data structure in case of copying).
Here is the code. This is pretty ugly, and I was not going to publish it, but no one answered at the time of writing, so I can only hope that someone can help make it better.
//Checks if list (or element) S has element of type T template<class L, class T> struct HasElem : std::is_same<L,T>{}; template<template<class,class> class Node, class T, class NodeT, class Next> struct HasElem<Node<NodeT,Next>,T>{ static constexpr bool value = std::is_same<NodeT,T>::value || HasElem<Next,T>::value; }; template<template<class> class Leaf, class S, class T> struct HasElem<Leaf<S>,T> : std::is_same<S,T>{}; //Push type transform template<class N, class T> struct Push{}; template<template<class,class> class Node, class T, class Next, class U> struct Push<Node<T,Next>,U>{ typedef Node<U,Node<T,Next>> type; }; //Node type template<class T, class Next> struct Node{ Node(Next&& nxt) : next(nxt){} Node(const Next& nxt) : next(nxt){} std::stack<T> st; Next next; //Pushing a new type onto the stack template<class U> typename std::enable_if<!HasElem<Node,U>::value,typename Push<Node,U>::type>::type push(const U& u) &&{ //disallow pushing new types on lvalues return typename Push<Node,U>::type(std::move(*this)).push(u); } //Pushing a new type onto the stack as an lvalue and return a copy template<class U> typename std::enable_if<!HasElem<Node,U>::value,typename Push<Node,U>::type>::type push_new(const U& u) const{ //cannot overload on && qualifier. Make the name uglier to warn of the cost return typename Push<Node,U>::type(*this).push(u); } //Regular old push Node& push(const T& t){ st.push(t); return *this; } //Push onto another node in the list template<class U> typename std::enable_if<HasElem<Node,U>::value,Node>::type push(const U& u){ next.push(u); return *this; } template<class U> typename std::enable_if<std::is_same<T,U>::value,U>::type& top(){ return st.top(); } template<class U> typename std::enable_if<!std::is_same<T,U>::value && HasElem<Node,U>::value,U>::type& top(){ return next.top<U>(); } }; //The last node. I made it hold data but it doesn't need to template<class T> struct Leaf{ std::stack<T> st; Leaf& push(const T& t){ st.push(t); return *this; } template<class U> Node<U,Leaf> push(const U& u){ return Node<U,Leaf>(std::move(*this)).push(u); } template<class U> void top(){} T& top(){ return st.top(); } void pop(){ st.pop(); } };
Here is an example of using and hiding the difference between push and push_new .
template<class T, class Next, class U> auto push(Node<T,Next>&& n, const U& u) -> decltype(n.push(u)){ return n.push(u); } template<class T, class Next, class U> auto push(const Node<T,Next>& n, const U& u) -> decltype(n.push_new(u)){ return n.push_new(u); } int main(){ auto b = Leaf<int>().push<int>(42).push<double>(3.14).push<char>('a'); auto a = push(b,(char*)"Hello");
The main disadvantage is that it will be ineffective if you save intermediate states (i.e. in variables), but if you combine operations together, for example b , in the example, you can avoid this.