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

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

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

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