Search code examples
c++templatesvariadic-templatesfold-expression

Unpacking a variadic template in C++ into a tuple in order to access the parameters with a structured binding


I have a class with variadic template parameters and I am trying to write a member function that unpacks the parameters into a tuple, so that I can use a structured binding in order to access the individual parameters.

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


struct TypeErasedParameter
{
private:
    const std::string m_strId;

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

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

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

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 ...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 is not correct yet, although the parameters provided are
    // the problem is the insertion of the elements I think
    if (!(pParamPack->m_mapParamsById.insert(std::make_pair(vecIds.at(Is), std::get<Is>(tuple))), ...))
    {
      return nullptr;
    }
    return pParamPack;
  }

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

    template<size_t... Is>
    std::tuple<Args& ...> UnpackParametersHelper( ParameterPack& parameters, std::index_sequence<Is...>)
    {
        const auto& vecIds = GetIds();
        // TODO: Return a tuple of the parameters by matching the Ids with the Is order of the parameters
        return std::make_tuple< Args& ...>((parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is))) ...);
    }

    std::tuple<Args& ...> UnpackParameters( ParameterPack& parameters)
    {
        // parameters contains as many Ids and types as Args... 
        return UnpackParametersHelper(parameters, std::index_sequence_for<Args...>{});
    }
};

int main(int argc, char* argv[])
{
    ParameterPack paramPack;
    paramPack.m_mapParamsById["doubleParam"] = std::make_unique<Parameter<double>>("doubleParam", 2.245);
    paramPack.m_mapParamsById["intParam"] = std::make_unique<Parameter<int>>("intParam", 5);

    ParameterUnpacker<double, int> unpacker({ "doubleParam" , "intParam" });
    auto[doubleparamout, intparamout] = unpacker.UnpackParameters(paramPack);
    return 0;    
}

Edit: The error I get when trying to compile is the following: "could not convert 'std::make_tuple(_Elements&& ...) [with _Elements = {double&, int&}]((* &(& parameters)->ParameterPack::GetParameter((* &(& vecIds)->std::vectorstd::__cxx11::basic_string<char >::at(1)))))' from 'tuple<double, int>' to 'tuple<double&, int&>' return std::make_tuple< Args& ...>((parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is))) ...);"

Thanks to @n. m. could be an AI I added a more concise and usable example with the error message, see http://coliru.stacked-crooked.com/a/e832f7f9c1994d7e

Thanks a lot in advance, let me know if you need any further information!


Solution

  • Your problem is std::make_tuple. The whole point of this function is to let you omit template arguments. It is created for pre-CTAD C++. You don't need it because (1) you were writing down the template arguments anyway, (2) we have CTAD now, and (3) the whole line is just unnecessarily convoluted.

    Change the offending line to this:

    return {parameters.GetParameter<Args>(vecIds.at(Is)) ...};