LINUX.ORG.RU

Шаблон не хочет автоматически выводить тип, как бы его заставить?

 ,


0

2

При использовании лямбд и function в параметре шаблонизированной функции, оно почему то не хочет автоматически выводить тип. Для примера:

template <class T>
void test(const std::function<void(T ii)> &task) {
    task(5);
}

...

    test<int>([&](int ii) { // так компилит
        qDebug() << ii;  });

    test([&](int ii) { // так не компилит
        qDebug() << ii;  });

no matching function for call to ‘test(main()::<lambda(int)>)’

candidate: ‘template<class T> void test(const std::function<void(T)>&)’
    6 | void test(const std::function<void(T ii)> &task) {
      |      ^~~~
template argument deduction/substitution failed:
‘main()::<lambda(int)>’ is not derived from ‘const std::function<void(T)>’

Если весь тип function заменить на auto, то компилит (c++20):

void test(const auto &task) {
    task(5);
}
Но так не хотелось бы, не наглядно. И явно каждый раз прописывать тип шаблон это много избыточности при многоэтажных типах.

Так же при использовании auto не позволяет сделать параметр со значением по умолчанию

void test(const auto& task = nullptr) {
    if (task) // error: could not convert ‘task’ from ‘const main()::<lambda(int)>’ to ‘bool’
        task(5);
}

Что нужно в шаблоне дописать, что бы все же можно было прописать тип function, а после вызывать без явного указания типов шаблона?



Последнее исправление: victor79 (всего исправлений: 1)

Ответ на: комментарий от LamerOk

Потому что иногда, сука, надо хотя бы читать, а не писать.
template< class R, class... Args > class function<R(Args...)>;

Все равно не понял. И цуки не помагают. Я без проблем использую другие функции с вариадик шаблоном без специализаций. Лямбда при вызове указывается с типами в параметрах - вывод типов возможен.

victor79
() автор топика
Ответ на: комментарий от victor79

Я без проблем использую другие функции с вариадик шаблоном без специализаций.

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

Алсо, мой коммент был про Шаблон не хочет автоматически выводить тип, как бы его заставить? (комментарий)

Лямбда при вызове указывается с типами в параметрах - вывод типов возможен.

Тебе конпелятор человеческим языком говорит:

template argument deduction/substitution failed: ‘main()::<lambda(int)>’ is not derived from ‘const std::function<void(T)>’

Что тебе не понятно?

LamerOk ★★★★★
()
Последнее исправление: LamerOk (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Ну приведи пример,
Что тебе не понятно?

Ты не аргументируешь свои слова, хамишь и плюешься. Зачем мне тратить время на приведение примеров. Сам поищи.

victor79
() автор топика
Ответ на: комментарий от victor79

Потому что вывести тип std::function из типа лямбды невозможно.

anonymous
()
Ответ на: комментарий от victor79

В случае с map:

  1. верхний тип совпадает и компилятор рекурсивно проверяет аргументы
  2. T == что угодно
  3. int == int
  4. успех

В случае с function:

  1. верхний тип не совпадает
  2. провал

В стандарте где-то расписан весь процесс матчинга, но то чтиво не для слабонервных.

Может deduction guides в данном случае могли бы помочь, но для стандартных шаблонов и встроенных типов их не стоит писать.

xaizek ★★★★★
()

Я бы что-то такое, наверное, попробовал:

#include <functional>
#include <iostream>

template <typename T>
concept isNullptr = std::is_same<std::decay_t<T>, std::nullptr_t>::value;

template <typename T>
concept optGoodTask = isNullptr<T> || requires(const T &t) {
     t(5);
};

template <typename F>
requires optGoodTask<F>
void test(F &&task) {
    if constexpr (!isNullptr<F>) {
        task(5);
    }
}

int main() {
    test(nullptr);
    test([&](double a) {
        std::cout << a << '\n';
    });
}
xaizek ★★★★★
()
Ответ на: комментарий от xaizek

В стандарте где-то расписан весь процесс матчинга, но то чтиво не для слабонервных.

Да, там без бутылки сложно разобраться, я сейчас попытался въехать, плюнул

Может deduction guides в данном случае могли бы помочь

Я тут позабавлялся, такое родил (я не претендую на лучшую реализацию, такое бы не заюзал, да и std::function в целом в данном случае):

#include <functional>
#include <type_traits>
#include <tuple>
using namespace std;

template<typename T> 
struct fn_traits;  
template<typename R, typename ...Args> 
struct fn_traits<std::function<R(Args...)>>
{
    static constexpr size_t args_cnt = sizeof...(Args);
    typedef R result_type;
    template <size_t i>
    struct arg {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

template <class T>
struct S {
	template <class U>
	S(U &&) {}
	static void test(const std::function<void(T ii)> &task) {}
};
template <class U>
S(U &&u) -> S<typename fn_traits<decltype(std::function(u))>::template arg<0>::type>;

#define call_test(fn) S(fn).test(fn)

int main() {
	call_test([](int){});  // ok
}

Не, ну зато не пишем это противное <int> ))

pavlick ★★
()
Последнее исправление: pavlick (всего исправлений: 2)

Шаблон не хочет автоматически выводить тип, как бы его заставить?

Перестать писать на говношаблонах?

AUX ★★★
()
Ответ на: комментарий от deep-purple

Да тут самовар нужен.

Но в данном случае шаблоны лишь иллюстрируют проблему чувака - он не в дугу с какими именно типами он работает.

LamerOk ★★★★★
()
Ответ на: комментарий от victor79

Всё, въехал. Если процитировать стандарт, то

Template argument deduction attempts to determine template arguments (types for type template parameters Ti, templates for template template parameters TTi, and values for non-type template parameters Ii), which can be substituted into each parameter P to produce the type deduced A, which is the same as the type of the argument A, after adjustments listed below. 

Другими словами, например у нас есть

template <typename T>
void fn(std::function<T> &&q);
...
fn(arg);

то цель вывода типа - получить получить такой тип параметра P (тип q в нашем случае), который будет идентичен типу аргументу A (тип arg в нашем случае). Никаких юзер-конвершин там нельзя, только стандартные вроде массив-в-указатель. Понятно, что без конверсий лямбда никак не превратится в function, поэтому и не работает. А так работает, тут конверсии не нужны:

fn( std::function([](int){}) );
pavlick ★★
()
Ответ на: комментарий от pavlick

fn( std::function([](int){}) );

Получается, что если фукнцию объявить

void test(auto &&task) {}
...
test([](){});
То в функцию передается лямбда по значению, без всяких function. Но при этом сделать переменную с явным объявлением типа лямбда для размещения в ней значения нельзя.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

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

    auto fn1 = [](){};
    auto fn2 = [](){};
    qDebug() << std::is_same<decltype(fn1), decltype(fn2)>::value;
    // out false

victor79
() автор топика
Ответ на: комментарий от victor79

Получается, что при авто выводе типов конструирования не происходит, несмотря на наличие подходящего конструктора.

   pair<int,int> ab2 = {5,3}; // так работает
   pair<auto,auto> ab3 = pair(5,3); // и так работает,
//   pair<auto,auto> ab1 = {5,3}; // а так не работает
//   pair<auto,auto> ab(5,3); // и так не работает.

И поэтому, шаблонизированная function в параметре не конструируется из лямбды при вызове. Только если function будет явно сконструирована при вызове:

fn( std::function([](int){}) );

В принципе вопрос закрыт, чуть позже еще допишу упомянутые здесь варианты.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

хм..

    pair ab1(1,2); // так работает,
//    pair<auto,auto> ab2(1,2); // а так нет
И в чем отличие? У auto свои правила вывода?

victor79
() автор топика
Ответ на: комментарий от Siborgium

Ну да, определен (я даже прочитал кусочек что это такое). Т.е. для pair без списка шаблонных аргументов вывод типов применяется. А при наличии списка, даже если аргументы auto, те правила не применяются.

В общем auto отчасти похож на типовую deduction, отчасти имеет свои правила.

victor79
() автор топика
Ответ на: комментарий от victor79

сделать переменную с явным объявлением типа лямбда для размещения в ней значения нельзя.

До C++20:

auto f = [] {};
decltype(f) f2 = f2;

C++20: decltype([] {}) f3;.

anonymous
()
Ответ на: комментарий от anonymous
auto f = [] {};
decltype(f) f2 = []{}; // compile error
decltype([]{}) f2 = []{}; // compile error

Т.е. в f2 ничего не разместить кроме как саму же f (или оставить пустой, интересно что это будет). Потому что тип лямбды уникален для каждого нового значения лямбды. Т.е. это не больше чем алиас для f.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от AUX

Перестать писать на говношаблонах?

(на правах вброса)

А зачем в 21-м году нужны C++ без шаблонов? Да, они сделаны по-дурацки. Если не они, то тогда остается Rust, где генерики и ограничения по трейтам, будучи содранными из хаскеля чуть ли не на 90%, выглядят много проще и понятнее, чем странные шаблоны C++? Что делать-то тогда? Сухари сушить?)

anonymous
()
Ответ на: комментарий от AUX

Генерики ненужны, в go без них прекрасно живётся.

Поэтому их туда наконец добавили?

anonymous
()
Ответ на: комментарий от anonymous

Странное у вас представление о хорошем. Все познается в сравнении. Если вляпаетесь в задачу строго не выше С++03, то тогда не будете говорить, что шаблоны С++20 дурацкие или говношаблоны.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.