3

I have a vector of strings, each of which is the result of applying std::to_string to some basic datatype (eg char, int, double). I would like a function to undo this into a tuple of the appropriate types.

I have a simple function template to invert std::to_string:

template<typename T>
T from_string(std::string s)
{
}

template<>
int from_string<int>(std::string s)
{
    return std::stoi(s);
}

template<>
double from_string<double>(std::string s)
{
    return std::stod(s);
}

//... and more such specializations for the other basic types

I want a function like:

template<typename... Ts>
std::tuple<Ts> undo(const std::vector<std::string>>& vec_of_str)
{
    // somehow call the appropriate specializations of from_string to the elements of vector_of_str and pack the results in a tuple. then return the tuple.     
}

The function should behave like this:

int main()
{
    auto ss = std::vector<std::string>>({"4", "0.5"});
    auto tuple1 = undo<int, double>(ss);
    std::tuple<int, double> tuple2(4, 0.5);

    // tuple1 and tuple2 should be identical. 
}

I think that I have to "iterate" over the parameters in Ts (perhaps the correct term is "unpack"), call the previous function, from_string for each one, and then package the results of each application of from_string into a tuple. I've seen (and used) examples that unpack a template parameter pack - they are usually recursive (but not in the usual way of a function calling itself), but I don't see how to do the rest.

sitiposit
  • 149
  • 1
  • 13
  • You would have to know what the original types were. And this sounds like an XY problem. –  Jun 26 '18 at 16:12
  • 1
    You're going to have to parse the string to figure out what data type it represents (is it an `int` or a `float` or a `double` for example). – Carl Jun 26 '18 at 16:13
  • I edited the code to express that the types needed to interpret the strings are specified as template parameters to the function undo. Does that address your concerns? – sitiposit Jun 26 '18 at 16:16
  • Sadly, tuples are entirely compile-time resolved constructs and vectors are runtime. It is not possible to generate an arbitrary tuple from an arbitrary length vector. On the other hand, if the vector was constructed from a tuple type, and you still have access to that type, it may be possible to stuff the same values back in to an instance of that specific tuple type. – Gem Taylor Jun 26 '18 at 16:22

2 Answers2

3

An example:

#include <vector>
#include <string>
#include <tuple>
#include <cassert>

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/trim.hpp>

template<class... Ts, size_t... Idxs>
std::tuple<Ts...>
parse(std::vector<std::string> const& values, std::index_sequence<Idxs...>) {
    return {boost::lexical_cast<Ts>(boost::algorithm::trim_copy(values[Idxs]))...};
}

template<class... Ts>
std::tuple<Ts...> undo(std::vector<std::string> const& values) {
    assert(sizeof...(Ts) == values.size());
    return parse<Ts...>(values, std::make_index_sequence<sizeof...(Ts)>{});
}

int main() {
    auto ss = std::vector<std::string>({"4", "0.5"});
    auto tuple1 = undo<int, double>(ss);
    std::tuple<int, double> tuple2(4, 0.5);
    std::cout << (tuple1 == tuple2) << '\n';
    assert(tuple1 == tuple2);
}

If the string values do not contain leading and/or trailing whitespace, then that call to boost::algorithm::trim_copy can be removed. It is there because boost::lexical_cast fails on whitespace.


Without boost::lexical_cast you will need to re-implement it, something like:

template<class T> T from_string(std::string const& s);
template<> int      from_string<int>(std::string const& s)    { return std::stoi(s); }
template<> double   from_string<double>(std::string const& s) { return std::stod(s); }
// And so on.

template<class... Ts, size_t... Idxs>
std::tuple<Ts...>
parse(std::vector<std::string> const& values, std::index_sequence<Idxs...>) {
    return {from_string<Ts>(values[Idxs])...};
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Thanks Maxim. I must say that I don't quite understand how this works, but from the main function, it does exactly what I asked for. Is there any way to get this done without boost? – sitiposit Jun 26 '18 at 16:59
  • @sitiposit Added an example without boost for you. – Maxim Egorushkin Jun 26 '18 at 17:06
  • It can be simplified a bit using `std::index_sequence`, `std::index_sequence_for`, and removing the `std::tuple` from the `return`. – Acorn Jun 26 '18 at 17:27
  • Wow. This answer is succinct, correct (I tested it) and it came within an hour of my post. Thanks so much. – sitiposit Jun 26 '18 at 17:34
  • @sitiposit - a possible, a little more generic, `from_string()`: `template T from_string (std::string const & str) { std::istringstream iss{str}; T ret; iss >> ret; return ret; }` – max66 Jun 26 '18 at 19:39
  • @max66 That is similar to what `boost::lexical_cast` does. Only your version does not have any error handling and hence should never be used. – Maxim Egorushkin Jun 27 '18 at 09:41
  • @MaximEgorushkin - my suggestion was for a generic alternative for `for_string()` in you no-boost example, not an alternative for your boost example. – max66 Jun 27 '18 at 12:21
2

For C++11 -- useful if you don't have C++14 (required by Maxim's solution), or in case you want to learn to implement recursive variadic templates:

#include <string>
#include <vector>
#include <tuple>
#include <cassert>

template <std::size_t N, typename T>
struct Undo
{
    static void f(T& tuple, const std::vector<std::string>& vec_of_str)
    {
        Undo<N - 1, T>::f(tuple, vec_of_str);
        std::get<N - 1>(tuple) = from_string<
            typename std::tuple_element<N - 1, T>::type
        >(vec_of_str[N - 1]);
    }
};

template <typename T>
struct Undo<0, T>
{
    static void f(T&, const std::vector<std::string>&)
    {
    }
};

template <typename... Ts>
std::tuple<Ts...> undo(const std::vector<std::string>& vec_of_str)
{
    assert(vec_of_str.size() == sizeof...(Ts));
    std::tuple<Ts...> ret;
    Undo<sizeof...(Ts), std::tuple<Ts...>>::f(ret, vec_of_str);
    return ret;
}
Acorn
  • 24,970
  • 5
  • 40
  • 69