Home > Net >  C 17 Expand a Fold Expression (as pairs of values)
C 17 Expand a Fold Expression (as pairs of values)

Time:04-04

I am writing some code to experiment with fold expressions.

I don't think my approach to solving this problem is the best possible approach.

I am trying to write a function which "does something" with pairs of values obtained from expanding a parameter pack. The first type is always a std::string, the second type can vary.

Here is my code:

#include <memory>
#include <map>
#include <string>

class ThingBase
{
};

template<typename T>
class Thing : public ThingBase
{
    public:
    Thing(T data)
        : data(data)
    {
    }

    T data;
};

std::map<std::string, std::shared_ptr<ThingBase>> the_map;

template<typename T, typename... Args>
static void extract(std::string& name_out, T& value_out,
                    std::string& name_in, T& value_in,
                    Args... values)
{
    name_out = name_in;
    value_out = value_in;
}

template<typename T, typename... Args>
void test_function(Args... args)
{
    // expect arguments in pairs like this
    std::string name;
    T value;

    (extract(name, value, args), ...);

    the_map.insert(name, std::make_shared(Thing<T>(value)));
}

int main()
{
    test_function("argument 1", 1, "argument 2", 2, "argument 3", 3);
    test_function("argument 1", 1, "argument 2", 2, "argument 3", "three");

    return 0;
}

A few comments:

  • I didn't know of a way to "expand" the parameter pack without writing a function, which does the expanding due to how the parameters to that function are defined. (See extract function.)
  • There may be a way to do it without this function. It seems like a simple thing to want to do, and writing an additional function which just copies some values from one argument to another seems like a bodge rather than a proper solution.
  • The code doesn't actually compile in its current state. Here's why:
fold_expression_test.cpp:52:68: error: no matching function for call to ‘test_function(const char [11], int, const char [11], int, const char [11], int)’
   52 |     test_function("argument 1", 1, "argument 2", 2, "argument 3", 3);
      |                                                                    ^
fold_expression_test.cpp:36:6: note: candidate: ‘template<class T, class ... Args> void test_function(Args ...)’
   36 | void test_function(Args... args)
      |      ^~~~~~~~~~~~~
fold_expression_test.cpp:36:6: note:   template argument deduction/substitution failed:
fold_expression_test.cpp:52:68: note:   couldn’t deduce template parameter ‘T’
   52 |     test_function("argument 1", 1, "argument 2", 2, "argument 3", 3);
      |                                                                    ^
fold_expression_test.cpp:53:74: error: no matching function for call to ‘test_function(const char [11], int, const char [11], int, const char [11], const char [6])’
   53 |     test_function("argument 1", 1, "argument 2", 2, "argument 3", "three");
      |                                                                          ^
fold_expression_test.cpp:36:6: note: candidate: ‘template<class T, class ... Args> void test_function(Args ...)’
   36 | void test_function(Args... args)
      |      ^~~~~~~~~~~~~
fold_expression_test.cpp:36:6: note:   template argument deduction/substitution failed:
fold_expression_test.cpp:53:74: note:   couldn’t deduce template parameter ‘T’
   53 |     test_function("argument 1", 1, "argument 2", 2, "argument 3", "three");
      |                                                                          ^
  • It may be better/easier to understand code written with a constexpr-if (if constexpr) statement. The disadvantage of this is the compiler generates a large number of functions, one for each unique number of parameters.

  • If it isn't clear from the code, it doesn't actually matter what is being done with the arguments. The point here is to express the intent to expand a parameter pack using a fold-expression where arguments should be unfolded in pairs. You can ignore all the stuff relating to std::map if you find that obfuscating.

The problem can be "solved" by using a constexpr-if. But can it be done using the fold-expression?

template<typename T, typename... Args>
void test_function(const std::string& name, const T& value, Args... values)
{
    the_map.insert({name, std::make_shared<Thing<T>>(value)});

    if constexpr(sizeof...(values) > 0)
    {
        test_function(values...);
    }
}

CodePudding user response:

Making the function recursive and picking out two parameters at a time can be one approach.

#include <string>
#include <iostream>

template<typename T, typename... Rest>
void test_function(const std::string& str, T&& value, Rest&&... rest)
{
    std::cout << str << " : " << value << "\n";
    if constexpr (sizeof...(Rest) > 0) {
        test_function(std::forward<Rest>(rest)...);
    }
}

int main()
{

    test_function("argument 1", 1, "argument 2", 2, "argument 3", 3);
    test_function("argument 1", 1, "argument 2", 2, "argument 3", "three");

    return 0;
}

Here I used if constexpr to break the recursion, but an overload that takes no parameters could also work.

CodePudding user response:

The problem can be "solved" by using a constexpr-if. But can it be done using the fold-expression?

You can pack args... into a tuple and access the corresponding key and value by index, for example

template<std::size_t... Is, typename Tuple>
void test_function_impl(std::index_sequence<Is...>, Tuple t) {
  (the_map.insert({
    std::get<Is * 2>(std::move(t)), 
    std::make_shared<
      Thing<std::tuple_element_t<Is * 2   1, Tuple>>>(
        std::get<Is * 2   1>(std::move(t)))}),
   ...);
}

template<typename... Args>
void test_function(Args... args) {
  test_function_impl(
    std::make_index_sequence<sizeof...(Args) / 2>{}, 
    std::tuple(std::move(args)...));
}

Demo

CodePudding user response:

Just had a thought based on this

https://riptutorial.com/cplusplus/example/3208/iterating-over-a-parameter-pack

Only an initial idea (untested as I do not have time to test this evening) - possibly something like this:

struct group
{
    std::string;
    double;
}

// copied from above link
template <class... Ts>
void print_all(std::ostream& os, Ts const&... args) {
    using expander = group[];
    (void)expander{0,
        (void(os << args), 0)...
    };
}

Not sure if this will work - will try and test it sometime this week

  • Related