Template && reference
От: AnatolyDu  
Дата: 13.02.22 15:40
Оценка:
Что-то я не "догоняю"...

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

template<typename... TArguments>
using FunctionToCall = void (*) ( TArguments...);


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

// тестовый стринг
std::string strString = "TestString";
const std::string &strParam = strString;

Так вот, если я передаю в качестве аргумента функцию, принимающую pointer :

void fPointer(const std::string * a_strString)
{
std::cout << "fPointer: " << *a_strString;
}

и сам вызов:
fCaller( &fPointer, &strParam );
то, всё компилируется и отрабатывает как надо...

а если с reference:

void fReference(const std::string & a_strString)
{
std::cout << "fReference: " << a_strString;
}

вызов:
fCaller( &fReference, strParam );

то, код не компилируется:

error: no matching function for call to 'fCaller(void (*)(const string&), const string&)'
37 | fCaller( &fReference, strParam );
| ^
/tmp/main.cpp:24:6: note: candidate: 'template<class ... TArguments> void fCaller(FunctionToCall<TArguments ...>, TArguments ...)'
24 | void fCaller( FunctionToCall<TArguments...> a_FuncToCall, TArguments ... arguments)
| ^~~~~~~
/tmp/main.cpp:24:6: note: template argument deduction/substitution failed:
/tmp/main.cpp:37:36: note: inconsistent parameter pack deduction with 'const std::__cxx11::basic_string<char>&' and 'std::__cxx11::basic_string<char>'
37 | fCaller( &fReference, strParam );


Что не так с reference ?
Re: Template && reference
От: reversecode google
Дата: 13.02.22 15:52
Оценка:
&& забыли
ну и std::forward очень не помешает

void fCaller( FunctionToCall<TArguments...> a_FuncToCall, TArguments &&... arguments)
Re: Template && reference
От: σ  
Дата: 13.02.22 17:08
Оценка:
AD>Что-то я не "догоняю"...
AD>Что не так с reference ?

А как можно вывести одинаковый тип из `const std::string&` и `const std::string`?
Re: Template && reference
От: Андрей Тарасевич Беларусь  
Дата: 13.02.22 19:10
Оценка: 2 (1) +2
Здравствуйте, 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`.
Best regards,
Андрей Тарасевич
Отредактировано 13.02.2022 19:30 Андрей Тарасевич . Предыдущая версия . Еще …
Отредактировано 13.02.2022 19:30 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 19:29 Андрей Тарасевич . Предыдущая версия .
Re[2]: Template && reference
От: AnatolyDu  
Дата: 13.02.22 20:00
Оценка:
Здравствуйте, reversecode, Вы писали:

R>&& забыли

R>ну и std::forward очень не помешает

R>void fCaller( FunctionToCall<TArguments...> a_FuncToCall, TArguments &&... arguments)


Большое спасибо !
Вариант с && и с std::forward отлично сработал !
Re[2]: Template && reference
От: AnatolyDu  
Дата: 13.02.22 20:09
Оценка:
Здравствуйте, σ, Вы писали:

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

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

σ>А как можно вывести одинаковый тип из `const std::string&` и `const std::string`?


А где Вы увидели const std::string без reference (&) ?
Я везде успользую только const std::string& — и как аргумент функции-pointer'a и как аргумент функции fCaller
Re[2]: Template && reference
От: AnatolyDu  
Дата: 13.02.22 20:52
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Здравствуйте, AnatolyDu, Вы писали:


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

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

Большое спасибо за подробный ответ !!!
Но, пару вопросов всё-таки есть

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


А почему из второго аргумента выводится std::string, а не const std::string& ?
Ведь он имеет явный тип const std::string& — const std::string &strParam ?

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


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


Я как раз и хочу, что бы компилятор указал мне на это несоответстие — мне нужно, что всё полностью совпадало...

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


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


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


Такой вариант меня вполне устраивает — с "замаскированным" указателем на функцию через аргумент шаблона... ну, если закрыть глаза на то, что char вместо int отлично скомпилируется...



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


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


У меня пока что не C++20

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


АТ>Но, еще раз, мне кажется что лучше было бы положиться на обычный duck typing для типа функтора и сделать обычный форвардинг всех параметров. А если вам хочется как-то ограничить разнообразие допустимых типов функторов, то сделать это можно уже поверх: через концепты или `std::enable_if`.
Re[3]: Template && reference
От: σ  
Дата: 13.02.22 21:04
Оценка:
AD>>>Что-то я не "догоняю"...
AD>>>Что не так с reference ?

σ>>А как можно вывести одинаковый тип из `const std::string&` и `const std::string`?


AD>А где Вы увидели const std::string без reference (&) ?


fCaller( &fReference, strParam );
______________________^^^^^^^^
Re[3]: Template && reference
От: Андрей Тарасевич Беларусь  
Дата: 13.02.22 21:30
Оценка: +2
Здравствуйте, AnatolyDu, Вы писали:

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


AD>А почему из второго аргумента выводится std::string, а не const std::string& ?

AD>Ведь он имеет явный тип const std::string& — const std::string &strParam ?

Здесь работает два момента:

1. Аргумент `strParam`, который вы передаете в вызов — это выражение. Правила обработки выражений в С++ говорят, что если выражение имеет ссылочный тип, то этот ссылочный тип немедленно теряется и все дальнейшее рассмотрение делается для нессылочного типа: http://eel.is/c++draft/expr.type#1

То есть согласно этому пункту ваш тип `const std::string &` сразу превращается в тип `const std::string`

2. Далее, в описании процесса дедукции шаблонных аргументов говорится, что для нессылочных параметров функции верхние cv-квалификаторы ее аргументов игнорируются: http://eel.is/c++draft/temp.deduct.call#2.3

Согласно этому пункту для целей дедукции ваш тип `const std::string` превращается в тип `std::string`.

По этой причине нет никакой разницы, указывать ли в качестве аргумента `strString` или `strParam` — дедукция в обоих случаях дедуцирует один и тот же тип `std::string`. Как часто говорят, именованную ссылку в языке С++ можно рассматривать как альтернативное имя для того же самого объекта. Язык С++ специально предпринимает усилия для того, чтобы скрыть различия между именованным объектом и именованной ссылкой на этот же объект. Оба способа доступа ведут себя внешне одинаково. Это вы и наблюдаете и в данном случае тоже.

То есть, если я ничего не упускаю, если у вас есть шаблон

template <typename T> void foo(T t) {}


то как вы не вертитесь, какой тип аргумента не предавайте, вы не сможете заставить дедукцию шаблонных аргументов дедуцировать `T` как ссылочный тип или как cv-квалифицированный тип. Такие типы в качестве `T` можно указать явно, но получить их через дедукцию — невозможно.

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


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


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


AD>У меня пока что не C++20


C++20 тут чисто номинально — для `std::type_identity_t`. Никто вам даже в С++98 не мешает написать самостоятельно

template <typename T>
struct non_deduced
{
  typedef T type;
};


и далее использовать `typename non_deduced<...>::type` вместо `std::type_identity_t<...>`.
Best regards,
Андрей Тарасевич
Отредактировано 14.02.2022 3:29 Андрей Тарасевич . Предыдущая версия . Еще …
Отредактировано 13.02.2022 21:46 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:44 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:44 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:43 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:39 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:38 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:32 Андрей Тарасевич . Предыдущая версия .
Отредактировано 13.02.2022 21:31 Андрей Тарасевич . Предыдущая версия .
Re[4]: Template && reference
От: AnatolyDu  
Дата: 14.02.22 07:04
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Здравствуйте, AnatolyDu, Вы писали:


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


AD>>А почему из второго аргумента выводится std::string, а не const std::string& ?

AD>>Ведь он имеет явный тип const std::string& — const std::string &strParam ?

АТ>Здесь работает два момента:


АТ>1. Аргумент `strParam`, который вы передаете в вызов — это выражение. Правила обработки выражений в С++ говорят, что если выражение имеет ссылочный тип, то этот ссылочный тип немедленно теряется и все дальнейшее рассмотрение делается для нессылочного типа: http://eel.is/c++draft/expr.type#1


АТ>То есть согласно этому пункту ваш тип `const std::string &` сразу превращается в тип `const std::string`


АТ>2. Далее, в описании процесса дедукции шаблонных аргументов говорится, что для нессылочных параметров функции верхние cv-квалификаторы ее аргументов игнорируются: http://eel.is/c++draft/temp.deduct.call#2.3


АТ>Согласно этому пункту для целей дедукции ваш тип `const std::string` превращается в тип `std::string`.


Да, всё чётко... Именно об этом и говорит компилятор :

/tmp/main.cpp:24:6: note: template argument deduction/substitution failed:
/tmp/main.cpp:37:36: note: inconsistent parameter pack deduction with 'const std::__cxx11::basic_string<char>&' and 'std::__cxx11::basic_string<char>'

проделав 2, указанные Вами, преобразования...

АТ>По этой причине нет никакой разницы, указывать ли в качестве аргумента `strString` или `strParam` — дедукция в обоих случаях дедуцирует один и тот же тип `std::string`. Как часто говорят, именованную ссылку в языке С++ можно рассматривать как альтернативное имя для того же самого объекта. Язык С++ специально предпринимает усилия для того, чтобы скрыть различия между именованным объектом и именованной ссылкой на этот же объект. Оба способа доступа ведут себя внешне одинаково. Это вы и наблюдаете и в данном случае тоже.


АТ>То есть, если я ничего не упускаю, если у вас есть шаблон


АТ>
АТ>template <typename T> void foo(T t) {}
АТ>


АТ>то как вы не вертитесь, какой тип аргумента не предавайте, вы не сможете заставить дедукцию шаблонных аргументов дедуцировать `T` как ссылочный тип или как cv-квалифицированный тип. Такие типы в качестве `T` можно указать явно, но получить их через дедукцию — невозможно.


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


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


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


AD>>У меня пока что не C++20


АТ>C++20 тут чисто номинально — для `std::type_identity_t`. Никто вам даже в С++98 не мешает написать самостоятельно


АТ>
АТ>template <typename T>
АТ>struct non_deduced
АТ>{
АТ>  typedef T type;
АТ>};
АТ>


АТ>и далее использовать `typename non_deduced<...>::type` вместо `std::type_identity_t<...>`.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.