I'm working on a program with following structure:
#include <iostream>
#include <string>
void fun(const std::string &text, int a, int b) { // (1)
std::cout << text << a + b << std::endl;
}
template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args) {
fun(args...);
}
void init(const std::string &text, int a, int b) {
execute(fun, text, a, b);
}
int main() {
init("Fun: ", 1, 2);
return 0;
}
and I get the error message
.code.tio.cpp:14:2: error: no matching function for call to 'execute'
execute(fun, text, a, b);
^~~~~~~
.code.tio.cpp:9:6: note: candidate template ignored: deduced conflicting types for parameter 'Args' (<const std::__cxx11::basic_string<char> &, int, int> vs. <std::__cxx11::basic_string<char>, int, int>)
void execute(void(*fun)(Args...), Args ...args) {
^
1 error generated.
I can fix the error by removing the reference in line (1):
void fun(const std::string text, int a, int b) {
but I want to pass the values by reference and not by value. The function template
template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args)
must not be changed. How can I fix this so that text
is passed by reference, execute
is not changed and init
is also not changed if possible?
EDIT:
@super showed that I was wrong and I have to reformulate my requirements. execute
can only be modified to the extent that other projects that have a dependency on this function don't break. I didn't think about such a solution.
Suggestion: use two set of template variadic parameters
template <typename ... As1, typename ... As2>
void execute(void(*fun)(As1...), As2 ... args) {
fun(args...);
}
This way you can maintain the reference in the fun()
function argument and pass to it a string value.
More in general: it's a nightmare impose that the deduced set of the arguments of the function is exactly the same set of the following arguments. And it's not necessary.
Suppose you have function foo()
that receive a long
void foo (long)
{ }
and you call execute()
passing a the foo()
pointer and a int
execute(foo, 1);
If you use a single Args...
variadic sequence, the call fail as in your question because the compiler deduce Args...
as long
(from foo()
signature) and long
(from the value 1
), so an ambiguity.
If you use two variadic sequences, the compiler deduce long
for As1...
, deduce int
for As2...
, there is no ambiguity and execute()
pass a int
value to a function that expects a long
value and this is perfectly legal.
Without touching execute
, I think you have to change init()
. One way would be to explicitely pass the template argument (bypassing the argument deduction in order to transport the reference type information):
void init(const std::string &text, int a, int b) {
execute<const std::string&>(fun, text, a, b);
}
I'm not sure why you don't want to change execute
, but modifying it to use a separate template parameter for the callable would be the best approach in my opinion.
This has the added benefit that you could pass in any callable, like a lambda or a std::function
or functor.
Adding perfect forwarding is an additional good idea. The callable could arguably be forwarded as well to be as generic as possible.
#include <utility>
template<typename F, typename ...Args>
void execute(F fun, Args&& ...args) {
fun(std::forward<Args>(args)...);
}
If the signature of the function is important, and that's why you don't want to modify execute
there are ways to extract that from F
with type traits.
It doesn't work since one of the Arguments is const&
- as you probably already noticed. These keywords can be eliminated by creating a helper struct that holds a const reference:
#include <iostream>
#include <string>
#include <functional>
template<typename T>
struct const_ref {
const_ref(const T& value) : value(value) {}
const std::reference_wrapper<const T> value;
};
void fun(const_ref<std::string> text, int a, int b) {
std::cout << text.value.get() << a + b << std::endl;
}
template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args) {
fun(args...);
}
void init(const std::string &text, int a, int b) {
const_ref<std::string> refstring{ text };
execute(fun, refstring, a, b);
}
int main() {
init("Fun: ", 1, 2);
}
this way extecute()
isn't altered. It's also not too hard to maintain as additional parameters that should be const T&
can simply be declared const_ref<T>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With