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...);
}
}
Making the function recursive and picking out two parameters at a time can be one approach.
Here I used
if constexpr
to break the recursion, but an overload that takes no parameters could also work.