Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deduced conflicting types in template pack with reference

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.

like image 256
Thomas Sablik Avatar asked Mar 29 '19 09:03

Thomas Sablik


Video Answer


4 Answers

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.

like image 56
max66 Avatar answered Oct 19 '22 19:10

max66


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);
}
like image 25
Jodocus Avatar answered Oct 19 '22 18:10

Jodocus


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.

like image 3
super Avatar answered Oct 19 '22 19:10

super


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>.

like image 1
Stack Danny Avatar answered Oct 19 '22 20:10

Stack Danny