C ++ нет неявных преобразований при передаче аргументов, выведенных из указателя функции - PullRequest
1 голос
/ 07 июля 2019

Я пытаюсь создать интеллектуальную прокси-функцию, чтобы аргументы передавались как дано.

Вот код:

#include <utility>
#include <iostream>

void func(int foo) {
    std::cout<<foo<<"\n";
}

template<typename... ARGS>
void proxy( void(*callback)(ARGS...), ARGS&&... args) {
    callback( std::forward<ARGS>(args)... );
}

int main() {
    proxy( func, 17 );
    int foo = 17;
    proxy( func, foo );
}

Я надеялся, что, позволив разрешению шаблона выяснить аргументы func, я получу прокси с подписью void proxy( void(*)(int), int );. Таким образом, он должен принимать как первый вызов (int является значением r), ​​так и второй (int является значением l).

В действительности вышеприведенная программа завершается с:

so.cpp:16:5: error: no matching function for call to 'proxy'
    proxy( func, foo );
    ^~~~~
so.cpp:9:6: note: candidate template ignored: deduced conflicting types for parameter 'ARGS' (<int> vs. <int &>)
void proxy( void(*callback)(ARGS...), ARGS&&... args) {
     ^
1 error generated.

т.е. - невозможность неявного преобразования из int & в int.

Я что-то здесь не так делаю?

Ответы [ 2 ]

3 голосов
/ 07 июля 2019

Проблема в том, что есть два места для вывода ARGS.Вывод аргумента шаблона функции всегда выполняется для каждой пары параметр / аргумент функции в отдельности и затем комбинируется.

[temp.deduct.type]

2 В некоторых случаях вычет выполняется с использованием одного набора типов P и A, в других случаях будет набор соответствующих типов P и A.Вычисление типа выполняется независимо для каждой пары P / A, а затем выводимые значения аргументов шаблона объединяются.Если выведение типа не может быть выполнено для какой-либо пары P / A, или если для какой-либо пары вычет приводит к более чем одному возможному набору выводимых значений, или если разные пары дают разные выведенные значения, или если остается какой-либо аргумент шаблонане выводится и не указывается явно, вывод аргумента шаблона завершается неудачно.Тип параметра типа выводится только из границы массива, если он не выводится иначе.

Поскольку пакет параметров функции содержит ссылки для пересылки, он будет выводить либо int&, либо int в зависимостина значение категории соответствующего аргумента.В то время как тип функции (из которого также происходит вычет) может привести только к вычету int.Для значений l два вывода не совпадают, и поэтому замена не выполняется.

Ошибка замены не является ошибкой, она просто устраняет перегрузку из набора кандидатов.Но в вашем случае это единственный кандидат, так что это становится серьезной ошибкой.

Таким образом, вам нужно сделать два независимых вывода, это означает, что это два пакета.Это все равно даст нам int& против int, но мы можем добавить проверку SFIANE, которая проверяет типы после удаления ссылки.Это должно дать желаемое поведение.

template<typename... ARGS1, typename... ARGS2>
std::enable_if_t<(std::is_same_v<std::decay_t<ARGS1>, std::decay_t<ARGS2>> && ...)>
proxy( void(*callback)(ARGS1...), ARGS2&&... args) {
    callback( std::forward<ARGS2>(args)... );
}

Тип возвращаемого значения использует выражение сгиба для проверки каждой пары аргументов, но только после того, как оба cv-квалификатора и ссылочный тип удалены (это то, что decay_t являетсякроме всего прочего).Если проверка пройдена, то enable_if_t существует и составляет void (по умолчанию ::type составляет enable_if).

Здесь это live .

Если, тем не менее, вы решите, что хотите поддерживать конвертируемость, вышеприведенную проверку можно изменить, чтобы использовать std::is_convertible_v вместо std::is_same_v.

1 голос
/ 07 июля 2019

К сожалению, в двух местах, которые аргументы шаблона ARGS допускают разные типы для каждого вариационного расширения (и каждого отдельного аргумента).

Вам необходимо сделать аргументы (например, ARGS && ... args) зависимыми или статическими.Вы можете использовать утомительный метод, который, я считаю, сохраняет идеальную пересылку, который показан последним, потому что он довольно многословный.

Этот метод мой любимый, так как он очень хорошо работает с intelisense.В некоторых случаях этот метод не «совершенен вперед», но он почти всегда будет таким же, поэтому, может быть, он хорош для вас:):

#include <iostream>

using namespace std;

void func (int foo)
{
    std::cout << foo << "\n";
}

template <typename T>
struct Mirror
{
    using type = T;
};

template < typename ... FunctionArgs >
void proxy (void (*callback) (FunctionArgs ...), typename Mirror<FunctionArgs>::type ... args)
{
    callback (args ...);
}

int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}

Метод статического приведения:

#include <iostream>

using namespace std;

void func (int foo)
{
    std::cout << foo << "\n";
}

template < typename ... FunctionArgs, typename ... UsedArgs >
void proxy (void (*callback) (FunctionArgs ...), UsedArgs && ... args)
{
    callback (static_cast < FunctionArgs > (args) ...);
}

int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}

Детальный метод, я думаю, что он сохраняет все идеальные пересылки:

#include <iostream>

using namespace std;

void func (int foo)
{
    std::cout << foo << "\n";
}

template <typename T>
struct Mirror
{
    using type = T;
};

template < typename ... FunctionArgs, typename ... UsedArgs >
void proxy (void (*callback) (FunctionArgs ...), UsedArgs && ... args)
{
    callback ([](auto m, auto && a) -> decltype(auto)
    {
        if constexpr (std::is_reference_v<decltype(a)>)
        {
            return std::forward<decltype(a)>(a);
        }
        else
        {
            return static_cast<typename decltype(m)::type>(a);
        }
    }( Mirror<UsedArgs>{}, std::forward<FunctionArgs>(args) ) ... );
}

int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}
...