c++templatesvariadic-templatesfold-expression

How to create a tuple from a variadic template class and call a member function with each element as a parameter in C++?


I have a class with variadic template parameters and I am trying to write a member function that creates a container, that contains the variadic parameters. My approach is to create a tuple from the parameters in order to unfold the elements of the tuple and call a function for reach element that adds them to my container class, see the following code (sorry for quite a long example, it's a bit intricate and I don't know to to boil it down further):

#include <string>
#include <tuple>
#include <vector>
#include <map>
#include <memory>
#include <iostream>

// these are a couple of mock-up classes, basically we want to save entities of arbitrary parameters

// base class for a type erased parameter
struct TypeErasedParameter
{
private:
  const std::string m_strId;

protected:
  TypeErasedParameter(std::string id) : m_strId(id) {}

public:
  virtual ~TypeErasedParameter() {}
  const std::string & GetId()
  {
    return m_strId;
  }

  template <typename T>
  T& GetValue();
};

// implementation of the type erased parameter, storing the parameter T in parameterValue
template <typename T>
struct Parameter : public TypeErasedParameter
{
  T parameterValue;

  Parameter(const std::string id, T value) : TypeErasedParameter(id), parameterValue(value) {}
};

template <typename T>
T& TypeErasedParameter::GetValue()
{
  return dynamic_cast<Parameter<T>*>(this)->parameterValue;
}

// ParameterPack is basically a container that stores a bunch of TypeErasedParameters
struct ParameterPack
{
  std::map<std::string, std::unique_ptr<TypeErasedParameter>> m_mapParamsById;

  template<typename ParameterType>
  ParameterType& GetParameter(const std::string& strParamName)
  {
    return m_mapParamsById.at(strParamName)->GetValue<ParameterType>();
  }

  template<typename ParameterType>
  bool AddParameter(const std::string& strParamName, ParameterType& parameter)
  {
    m_mapParamsById.insert(std::make_pair(strParamName, parameter));
    return true;
  }
};

// actual class of interest, the creator and unpacker of parameter packs
template <typename ...Args>
struct ParameterUnpacker
{
public:
  std::vector<std::string> m_vecIds;

  ParameterUnpacker(std::vector<std::string> vecIds) : m_vecIds(vecIds) {}

  const std::vector<std::string>& GetIds()
  {
    return m_vecIds;
  }

  template <typename Tuple, std::size_t... Is>
  std::unique_ptr<ParameterPack> CreateParameterPackHelper(Tuple&& tuple, std::index_sequence<Is...>)
  {
    std::unique_ptr<ParameterPack> pParamPack = std::unique_ptr<ParameterPack>(new ParameterPack());
    const auto& vecIds = GetIds();

    // TODO: This line I'm not sure about
    // my plan is to unfold the tuple passed to this function and call
    // AddParameter for each element
    if (!(pParamPack->AddParameter(vecIds.at(Is), std::get<Is>(tuple)) && ...))
    {
      return nullptr;
    }
    return pParamPack;
  }

  // with this function we want to be able to pass the parameters to our Unpacker so that it creates a 
  // parameterPack with the parameters in it for us
  template <typename... InputArgs>
  std::unique_ptr<ParameterPack> CreateParameterPack(InputArgs&... args)
  {
    // TODO: This line I'm also not sure about
    // my way of thought is to create a tuple of the the variadic template and
    // have CreateParameterPackHelper handle the rest
    return CreateParameterPackHelper(std::tuple<InputArgs...>{}, std::index_sequence_for<InputArgs ...>{});
  }
};

int main(int argc, char* argv[]) {
  // create a new pack using ParameterUnpacker.CreateParameterPack()
  ParameterUnpacker<double, int> unpacker({ "doubleParam" , "intParam" });
  const double double_param = 1.123;
  const int int_param= 12;
  auto newParamPack = unpacker.CreateParameterPack(double_param , int_param);

  return 0;
}

Here is the code above as an example with the err message: http://coliru.stacked-crooked.com/a/f315a81cb02f18e0

What I am ultimately not sure about is, how do I assure that the variadic template with which the instance of the ParameterUnpacker is created (i.e. template <typename ...Args>), fits with the ones that I am passing to unpack.CreateParameterPack(firstParam, secondParam, ...) (i.e. the template <typename... InputArgs>)? How do know from which ones I am creating the tuple to pass on to CreateParameterPackHelper? And how do I add each parameter to the ParameterPack that unpacker.CreateParameterPack is creating?

Thanks a lot in advance for you help!

Update: I was able to solve my problem, also resolving the ambiuous usage of two sets of variadic template parmeters (the ...Args and ...InputArgs), @Jarod42's hint was critial I believe. Here is an updated version of the functional code: http://coliru.stacked-crooked.com/a/20fe1fcb6715bf4b


Solution

  • There are some mistakes:

    template <typename... InputArgs>
    std::unique_ptr<ParameterPack> CreateParameterPack(InputArgs&... args)
    {
        return CreateParameterPackHelper(std::tuple<InputArgs...>{args...},
        //                                                        ^^^^^^^
                                         std::index_sequence_for<InputArgs ...>{});
    }
    

    You forget to use args.

    template <typename ParameterType>
    bool AddParameter(const std::string& strParamName, const ParameterType& parameter)
    {
        m_mapParamsById[strParamName] = std::make_unique<Parameter<ParameterType>>(parameter));
        return true;
    }
    

    You have to construct a Parameter<ParameterType> here.

    Demo

    Your usage of TypeErasedParameter is risky, as you will have UB with mismatching type. std::any seems more appropriate:

    struct TypeErasedParameter
    {
    private:
      const std::string m_strId;
      std::any value;
    
    public:
      template <typename T>
      TypeErasedParameter(std::string id, const T& value) : m_strId(id), value(value) {}
    
      const std::string& GetId() { return m_strId; }
    
      template <typename T>
      T& GetValue() { return std::any_cast<T&>(value); }
    };
    
    struct ParameterPack
    {
      std::map<std::string, TypeErasedParameter> m_mapParamsById;
      // TypeErasedParameter no longer to be pointer
    
      template <typename ParameterType>
      ParameterType& GetParameter(const std::string& strParamName)
      {
        return m_mapParamsById.at(strParamName).GetValue<ParameterType>();
      }
    
      template<typename T>
      bool AddParameter(const std::string& strParamName, const T& parameter)
      {
        m_mapParamsById.emplace(strParamName, TypeErasedParameter(strParamName, parameter));
        return true;
      }
    };
    

    Demo