Boris
Boris

Reputation: 769

C++ wrapper template: how to pass it as a template parameter?

Question to C++ template gurus:
I have created two template "policies" (not sure if this is the right term), which implement storage of some value types in a vector of either dumb or smart pointers:

#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

template <typename T>
class DumbPtrVec
{
  std::vector<T*> m_vec;
public:
  using handle = size_t;

  ~DumbPtrVec() {
    std::for_each(begin(m_vec), end(m_vec), [](T* p){ delete p; });
  }

  handle AddElement(T* p) {
    const handle index = m_vec.size();
    m_vec.push_back(p);
    return index;
  }

  T* GetElement(const handle& i) {
    T* p = (i < m_vec.size())? m_vec[i] : nullptr;
    return p;
  }
};

template <typename T>
class SmartPtrVec
{
  std::vector<std::shared_ptr<T>> m_vec;
public:
  using handle = std::weak_ptr<T>;

  handle AddElement(T* p) {
    m_vec.emplace_back(p);
    return m_vec.back(); // gets converted to weak_ptr
  }

  T* GetElement(const handle& i) {
    T* p = (i.expired())? nullptr : i.lock().get();
    return p;
  }
};

template <typename T, template<typename> typename STORAGE>
class Storage
{
    STORAGE<T> m_values;
public:
    using handle = typename STORAGE<int>::handle;

    handle AddValue(T* v) { return m_values.AddElement(v); }
    T* GetValue(handle h) { return m_values.GetElement(h); }
};

int main()
{
    constexpr int N = 13;

    Storage<int, DumbPtrVec> d;
    auto dh = d.AddValue(new int(N));
    std::cout << *d.GetValue(dh) << " == " << N <<std::endl;

    Storage<int, SmartPtrVec> s;
    auto sh = s.AddValue(new int(N));
    std::cout << *s.GetValue(sh) << " == " << N << std::endl;

    return 0;
}

Everything works fine, so far.
Then I added a template wrapper, that replaces the element "handle" with a unique string and keeps a look-up table for converting strings back to the handles. If this class is explicitly derived from either DumbPtrVec or SmartPtrVec class, everything works, e.g. for SmartPtrVec:

template <typename T>
class StringHandleWrapper : SmartPtrVec<T>
{
    using super = typename SmartPtrVec<T>;
    using Str2HandleMap = std::unordered_map<std::string, typename super::handle>;

    Str2HandleMap m_Name2HandleMap;

public:
    using handle = std::string;

    handle AddElement(T* p) {
        typename super::handle elem = super::AddElement(p);

        static int counter = 0;  
        std::string uuid = std::to_string(++counter);

        m_Name2HandleMap[uuid] = elem;
        return uuid;
    }

    T* GetElement(const handle& uuid) {
        auto it = m_Name2HandleMap.find(uuid);
        return (it != m_Name2HandleMap.end())? super::GetElement(it->second) : nullptr;
    }
};

and successful invocation:

Storage<int, StringHandleWrapper> s;
std::string handle = s.AddValue(new int(N));    

But I can't figure out how to add a second template parameter, STORAGE, to StringHandleWrapper, so that it could wrap any of DumbPtrVec or SmartPtrVec...
If I change StringHandleWrapper to:

template <typename T, template<typename> typename STORAGE>
class StringHandleWrapper : STORAGE<T>
{
    using super = typename STORAGE<T>;
//... rest unchanged

then I can't figure out how to instantiate Storage class, as compiler complains about "too few template arguments":

Storage<int, StringHandleWrapper<SmartPtrVec>> s;

I hope I'm missing something simple...

Thank you for taking your time to look through my long question!

Upvotes: 0

Views: 591

Answers (2)

n. m. could be an AI
n. m. could be an AI

Reputation: 119877

Create another level of template for partial argument application:

template <template <typename, template<typename> typename> class W,
          template <typename> typename S>
struct Apply
{
    template <typename T> using type = W<T, S>;
};

Then instantiate Storage like this:

Storage<int, Apply<StringHandleWrapper, SmartPtrVec>::type> s;

Upvotes: 1

Boris
Boris

Reputation: 769

Just found the answer (it was indeed simple):
I needed to introduce two single-parameter templates

template<typename T> using StringDumbHandleWrapper  = StringHandleWrapper<T, DumbPtrVec>;
template<typename T> using StringSmartHandleWrapper = StringHandleWrapper<T, SmartPtrVec>;

and use the new names in Storage instantiation, e.g.

Storage<int, StringDumbHandleWrapper> s;

So much for the long question... :)

Upvotes: 0

Related Questions