Информация об изменениях

Сообщение Re: Template && reference от 13.02.2022 19:10

Изменено 13.02.2022 19:29 Андрей Тарасевич

Re: Template && reference
Здравствуйте, AnatolyDu, Вы писали:

AD>Что-то я не "догоняю"...


AD>Хочу написать функцию, получающую другую функцию( указатель на неё ) и её аргументы (разного количества и типов) :


Написание таких шаблонов часто приводит к тому, что вы будуте сталкиваться с проблемой несоответствия типоа переданных аргументов с типами параметров функции. В обыкновенном вызове функции такого соответствия и не требуется — вы можете саокойно передавать аргумент типа `char` в функцию, ожидающую параметра типа `int` и т.п. А когда речь заходит о дедукции шаюлонных аргументов, такие вольности уже не являются допустимыми.

Первый вопрос, возникающий в таких случаях: а зачем вам вообще понадобилось объявлять параметр `a_FuncToCall` как именно указатель на функцию? Почему бы вам не завязаться на свободный duck typing и не сделать просто

template<typename F, typename... TArguments>
void fCaller(F a_FuncToCall, TArguments ... arguments)
{
  a_FuncToCall( arguments... );
}


и забыть о всех проблемах? Такой вариант будет компилироваться для обоих ваших вызовов. (Я не хочу пока касаться вопроса о правильном форвардинге аргументов.)

Второй вопрос, возникающий в таких случаях: если уж вы зачем-то хотите, чтобы ваш параметр был именно указателем на функцию — ваше право. В такой ситуации для того, чтобы получше воспроизвести "натуральное" поведение функций можно пропробовать намеренно поместить типы параметров в non-deduced context

// C++20
template<typename... TArguments>
void fCaller( FunctionToCall<TArguments...> a_FuncToCall, std::type_identity_t<TArguments>... arguments)
{
  a_FuncToCall( arguments... );
}


В такой ситуаци типы `TArguments` будут дедацироваться исключительно из типа функции. Такой вариант тоже будет компилироваться для обоих ваших вызовов

Но, еще раз, мне кажется что лучше было бы положиться на обычный duck typing для типа функтора и сделать обычный форвардинг всех параметров. А если вам хочется как-то ограничить разнообразие допустимых типов функторов, то сделать это можно уже поверх: через концепты или `std::enable_if`.
Re: Template && reference
Здравствуйте, AnatolyDu, Вы писали:

AD>Что-то я не "догоняю"...

AD>Что не так с reference ?

Все не так. В С++ шаблонах, если некий дедуцируемый шаблонный параметр `T` можно успешно дедуцировать из нескольких аргументов функции, то все разультаты таких дедукуий должны точно совпадать. Малейшее несовпадение являтеся ошибкой. В вашем случае в варианте `fCaller( &fReference, strParam )` шаблонный параметр `TArguments` можно дедуцировать и из первого параметра функции, и из второго. Дедукция из первого дает `const std::string &`, а из втрого — просто `std::string`. Это — разные типы. Получаем неоднозначную дедукцию.

AD>Хочу написать функцию, получающую другую функцию( указатель на неё ) и её аргументы (разного количества и типов) :


Написание таких шаблонов часто приводит к тому, что вы будуте сталкиваться с проблемой несоответствия типоа переданных аргументов с типами параметров функции. В обыкновенном вызове функции такого соответствия и не требуется — вы можете саокойно передавать аргумент типа `char` в функцию, ожидающую параметра типа `int` и т.п. А когда речь заходит о дедукции шаюлонных аргументов, такие вольности уже не являются допустимыми.

Первый вопрос, возникающий в таких случаях: а зачем вам вообще понадобилось объявлять параметр `a_FuncToCall` как именно указатель на функцию? Почему бы вам не завязаться на свободный duck typing и не сделать просто

template<typename F, typename... TArguments>
void fCaller(F a_FuncToCall, TArguments ... arguments)
{
  a_FuncToCall( arguments... );
}


и забыть о всех проблемах? Такой вариант будет компилироваться для обоих ваших вызовов. (Я не хочу пока касаться вопроса о правильном форвардинге аргументов.)

Второй вопрос, возникающий в таких случаях: если уж вы зачем-то хотите, чтобы ваш параметр был именно указателем на функцию — ваше право. В такой ситуации для того, чтобы получше воспроизвести "натуральное" поведение функций можно попробовать намеренно поместить типы параметров в non-deduced context, то есть умышленно подавить дедукцию шаблонных параметров из `arguments`

// C++20
template<typename... TArguments>
void fCaller( FunctionToCall<TArguments...> a_FuncToCall, std::type_identity_t<TArguments>... arguments)
{
  a_FuncToCall( arguments... );
}


В такой ситуаци типы `TArguments` будут дедацироваться исключительно из типа функции. Такой вариант тоже будет компилироваться для обоих ваших вызовов

Но, еще раз, мне кажется что лучше было бы положиться на обычный duck typing для типа функтора и сделать обычный форвардинг всех параметров. А если вам хочется как-то ограничить разнообразие допустимых типов функторов, то сделать это можно уже поверх: через концепты или `std::enable_if`.