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)...));
}
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