Вывод конфликтующих типов в пакете шаблонов со ссылкой - PullRequest
5 голосов
/ 29 марта 2019

Я работаю над программой со следующей структурой:

#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;
}

и получаю сообщение об ошибке

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

Я могу исправить ошибку, удалив ссылку в строке (1) :

void fun(const std::string text, int a, int b) {

, но я хочу передать значения по ссылке, а не по значению.Шаблон функции

template<typename ...Args>
void execute(void(*fun)(Args...), Args ...args)

не должен быть изменен.Как я могу это исправить, чтобы text передавалось по ссылке, execute не изменялось и init также не изменялось, если это возможно?

РЕДАКТИРОВАТЬ: @super показал, что я ошибался, и у меня естьпереформулировать мои требования.execute можно изменить только до такой степени, чтобы другие проекты, зависящие от этой функции, не ломались.Я не думал о таком решении.

Ответы [ 4 ]

4 голосов
/ 29 марта 2019

Предложение: используйте два набора шаблонных переменных параметров

template <typename ... As1, typename ... As2>
void execute(void(*fun)(As1...), As2 ... args) {
    fun(args...);
}

Таким образом, вы можете сохранить ссылку в аргументе функции fun() и передать ей строковое значение.

Подробнеев общем: это кошмарное навязывание того, что выведенный набор аргументов функции является точно таким же набором следующих аргументов.И это не обязательно.

Предположим, у вас есть функция foo(), которая получает long

void foo (long)
 { }

, и вы вызываете execute(), передавая указатель foo() и int

execute(foo, 1);

Если вы используете одну вариабельную последовательность Args..., вызов не удастся, как в вашем вопросе, потому что компилятор выводит Args... как long (из foo() подпись) и long(из значения 1), поэтому двусмысленность.

Если вы используете две вариационные последовательности, компилятор выводит long для As1..., выводит int для As2..., нет никакой двусмысленностии execute() передает значение int в функцию, которая ожидает значение long, и это совершенно законно.

3 голосов
/ 29 марта 2019

Я не уверен, почему вы не хотите изменять execute, но изменение его для использования отдельного параметра шаблона для вызываемого объекта было бы лучшим подходом, на мой взгляд.

Это имеетдополнительное преимущество, которое вы можете передать в любой вызываемый объект, например лямбду, std::function или функтор.

Добавление отличной пересылки - еще одна хорошая идея.Вызываемый объект, возможно, может быть переадресован так, чтобы быть как можно более универсальным.

#include <utility>

template<typename F, typename ...Args>
void execute(F fun, Args&& ...args) {
    fun(std::forward<Args>(args)...);
}

Если важна сигнатура функции, и поэтому вы не хотите изменять execute, есть способыизвлечь это из F с типом черт.

3 голосов
/ 29 марта 2019

Не касаясь execute, я думаю, что вы должны изменить init(). Одним из способов было бы явным образом передать аргумент шаблона (обходя вывод аргумента для передачи информации о ссылочном типе):

void init(const std::string &text, int a, int b) {
    execute<const std::string&>(fun, text, a, b);
}
1 голос
/ 29 марта 2019

Это не работает, так как один из аргументов const& - как вы, наверное, уже заметили. Эти ключевые слова можно исключить, создав вспомогательную структуру, которая содержит ссылку на констант:

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

таким образом extecute() не изменяется. Это также не сложно поддерживать, поскольку дополнительные параметры, которые должны быть const T&, могут быть просто объявлены const_ref<T>.

...