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, а после вызывать без явного указания типов шаблона?

Но так не хотелось бы, не наглядно

а что тут не наглядного? Коллбеки в STL реализованы именно так, а если компилятору не понравится тип task, то он об этом сообщит.

std::function лучше вообще не использовать без очень хороших причин, тут их похоже нет

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

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

И не понятно, как его сделать со значением по умолчанию.

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

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

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

Значение по умолчанию сделай через перегрузку функции без параметров.

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

И если тебе коллбек никуда сохранять не надо, а просто сразу же вызвать, то лучше заменить шаблонным параметром.

Так ее же все равно нужно через параметр функции передавать, т.е. оно все равно в стеке размещается. Хотя нужно будет попробовать function прописать сразу в параметр шаблона, вдруг разберет что куда.

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

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

void test(const auto &task) {

Очень интересный вариант, впервые вижу, чтобы кто-то по константной ссылке функции принимал. Сразу могу сказать, что он поломается даже на

test([x=5](auto...) mutable { ++x; }) 

не занимайся ерундой с std::function и константной ссылкой, а почитай про «универсальные» ссылки и пиши

void test(auto && f) {
    // ...
}

Но так не хотелось бы, не наглядно.

И явно каждый раз прописывать тип шаблон это много избыточности при многоэтажных типах.

Так тебе ехать или шашечки?

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

Делай перегрузку по количеству аргументов.

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

Но проблема с дефаултным значением, что оно его проверять не хочет, т.е. if (task) ругается.

Если сделаешь перегрузку без параметров, то if (task) будет не нужен. У тебя будет отдельная функция, когда task не равен nullptr и отдельная, когда равен.

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

Но проблема с дефаултным значением, что оно его проверять не хочет, т.е. if (task) ругается.

В одной перегрузке значение всегда есть, в другой его всегда нет и используется дефолтное. Зачем там if?

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

Делай хорошо, плохо само получится.

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

Зачем «вдруг», если можно сделать нормально? Если сильно хочется ограничений, допиши static_assert(std::is_same_t<std::invoke_result<decltype(task), ......) себе.

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

void test(auto && f) {

Это я уже использую с прошлого твоего поста... Но суть от этого не меняется, в некоторых случаях хочется видеть что в параметрах task, и какие параметры у функции однотипные.

У меня объявление шаблонной функции выглядит вот так:

template <class A, class B, class C>
void _tls_useWithWaitKorValue(
        map<A, map<B, MyList<C>>> &mmm,
        const A &key,
        const B &spl,
        const C &val,
        const function<void(const B&, const C&)> &taskItems,
        const function<void(const B&)> &taskNewBunch
        ) {
И тут легко запутаться где что, вариантов mmm по коду много. А ловить логические ошибки на порядок сложней, чем синтаксические, особенно если они просто сказываются на +/- 0.1 у значений типа double.

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

Может концепты заюзать? Напишешь очевидные требования во время объявления. Я разок уже их заюзал:

template<typename T>
requires std::same_as<std::remove_reference_t<T>, Chart>
Collector::Collector(T &&chart) :
	...

Ну если настаиваешь на варианте с function, то тебе в сторону «User-defined deduction guides», наверное.

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

А ловить логические ошибки на порядок сложней, чем синтаксические, особенно если они просто сказываются на +/- 0.1 у значений типа double.

Эта проблема обычно вытекает из некорректного написания шаблонного кода.

Вот смотри, у тебя есть

void foo(auto && f) {
    int x = f();
    // ...
}

foo([](){ return 4.2; }); // x = 4;

если бы ты писал

void foo(auto && f) {
    int x{ f() };
    // ...
}

проблемы бы не было, была бы ошибка на этапе компиляции.

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

проблемы бы не было, была бы ошибка на этапе компиляции.

Там все гораздо сложней, это мат.статистика, там плюс с минус напутаешь в сотне таких же плюсов, и потому думаешь, это ошибка в предположениях, что так должно работать, или ошибся с операцией.

В общем, при наличии дополнительного параметра, проходит вот такое:

template <class T>
void _test(T a, const std::function<void(T)> &task) {
    if (task)
        task(5);
}

template <class T>
void test(T a, auto &&task) {
    _test<T>(a, task);
}
...
test(3, [&](int v) {
...
Мой вопрос это решает, хотя это конечно лишние объявления.

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

Этот подход совмещает минусы каждого из подходов. У вас остается std::function, остается отсутствие типов в сигнатуре, остается прописывание типов руками, и плюсом вы получаете необходимость дублировать объявления и получаете лишний вызов на каждую такую функцию, который может и не быть заинлайнен.

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

это мат.статистика, там плюс с минус напутаешь в сотне таких же плюсов, и потому думаешь, это ошибка в предположениях,

Как плюсы и минусы относятся к декларации функций?

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

Этот подход совмещает минусы каждого из подходов. У вас остается std::function, остается отсутствие типов в сигнатуре, остается прописывание типов руками, и плюсом вы получаете необходимость дублировать объявления и получаете лишний вызов на каждую такую функцию, который может и не быть заинлайнен.

Типы контролируются внутренним объявлением _test, а необходимость указывать типы при вызове убираются внешним объявлением test. Проблем со скорость выполнения здесь не будет, в данном случае узкость места в сложности логики.

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

Сложность логики к этому никак не относится. Вы просто усложняете жизнь себе и тем, кто будет это читать.

Ну если бы можно было бы и шашечки и ехать, то было бы и то и другое. И что бы был смысл это кому-либо читать, для этого нужно что бы это хоть как-то работало.

Я бы даже авто конвертации int/bool/double/nullptr позапрещал бы, но нет таких конструкций. Когда пишешь простые вещи, то там хочется проще и быстрей.

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

Ага, наверное они для засолки огурцов. Вообще можно даже без концептов проверку типов сделать, за 10 минут на коленках написал:

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


template <class T>
void test(T &&fn) 
{
	using fn_t = decltype(function(fn));
	static_assert(same_as<fn_t, function<void(int)>>, "wrong type of function");
}
void f(int) {}
int main() {
	test([](double){});       // error
	test([](int){return 0;}); // error
	test(f);                  // ok 
	test([](int){});          // ok
}

ПС: но можно и на концептах

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

Я бы даже авто конвертации int/bool/double/nullptr позапрещал бы, но нет таких конструкций. Когда пишешь простые вещи, то там хочется проще и быстрей.

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

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

Ага, наверное они для засолки огурцов.

Раз применения сводятся к выводу типов и засолке огурцов, то придется солить ими огурцы.

ПС: но можно и на концептах

Вперед, пиши на концептах проверку типов, я посмотрю.

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

static_assert(same_as<fn_t, function<void(int)>>, «wrong type of function»);

Здесь ты не используешь шаблонизированные параметры function. Без этого оно и без таких извращений нормально проходит.

В аналогичных конструкциях, мой пример будет таким:

template <class T>
void test(auto &&fn) {
    using fn_t = decltype(function(fn));
    static_assert(same_as<fn_t, function<void(T)>>, "wrong type of function");
Компиляцию не проверял, этот вариант по заморочкам не лучше моего.

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

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

Так же проблема, что тип void() есть, но использовать его можно только как параметр шаблона или форвардное объявление функций. Значением оно быть не хочет.

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

Да он в тысячу раз лучше твоего, ты используешь function, я нет (лишь в компайлтайме). Почему-то вариант со статик ассертом не работает в таком виде

static_assert(..., function<void(T)>>, ...);

со следующим пояснением:

note: the expression ‘is_same_v<_Tp, _Up> [with _Tp = std::function<void()>; _Up = std::function<void()>]’ evaluated to ‘false’

Баг? По-моему да.
UPD: туплю там же типы разные. Ты сформулируй задачу конкретно: тебе нужна функция, которая принимает функтор с 1 аргументом лбого типа и возвращает void, я так понимаю. Сейчас накостылю.

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

Специально взял и проверил. Генерит ошибку:

#include <functional>
#include <QDebug>

template <class T>
void test(auto &&task) {

    using fn_t = decltype(task);
    static_assert(std::same_as<fn_t, std::function<void(T)>>, "wrong type of function");

    task(5);
}

int main() {
    test([&](int ii) {
        qDebug() << ii; });
    return 1;
}
До wrong type даже не доходит, говорит:
template argument deduction/substitution failed:
couldn’t deduce template parameter ‘T’

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

Потому что он и не должна так работать, там получается function с функтором внутри, а не типом аргумента. Сформулируй условия, которым должна отвечать функция (какой функтор должна принять, где должна не компилится). Тогда попробую написать.

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

Клоун, у него С++20. Тип там не выводится, потому что выводиться ему не из чего. Других экспертов по крестам у меня для вас нет.

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

Ты не напишешь ничего таким образом, потому что ты априори пытаешься делать какую-то чушь.

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

include <type_traits>
#include <utility>

template <typename F, typename R, typename ... Args>
concept invocable_as = std::is_invocable_r_v<F, Args...> && std::is_same_v<std::
invoke_result_t<F, Args...>, R>;

// примера ради 
using RetType = int; 
using ArgType1 = int;
using ArgType2 = int;

template <typename F>
void foo(F && f) requires invocable_as<F, RetType, ArgType1, ArgType2> {
    ArgType1 arg1;
    ArgType2 arg2;
    RetType var{ f(arg1, arg2) };
}
Siborgium ★★ ()
Ответ на: комментарий от Siborgium

Давно хотел спросить, можете ли разъяснить мне, почему вот здесь:

#include <functional>
#include <map>
#include <QDebug>

using std::map;
using std::function;

template <class T>
void test1(const function<void(T)> &task) {
    task(5);
}

template <class T>
void test2(const map<T,int> &m) {
    qDebug() << m.size();
}

int main() {
    test1<int>([](int ii) {});
    test2(map<int,int>());
    return 1;
}
test1() требует <int>, а test2() может без этого?

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

Да, и requires молча убирает функцию из overload set в случае, если она не удовлетворяет предикату. Т.е. какой-то ошибки у вас не будет, только длинный контекст трейс в случае если другого подходящего варианта не найдется.

Можно сделать, например, так

template <typename F>
void foo(F && f) {
    static_assert(invocable_as<F, RetType, ArgType1, ArgType2>);
    ArgType1 arg1;
    ArgType2 arg2;
    RetType var{ f(arg1, arg2) };
}

и оно будет работать так, как вы хотите, но, повторюсь, все это полная чушь, и смысла никакого не имеет. Проблема абсолютно надумана и буквально не существует. Если вас волнуют неявные преобразования, пишите свои врапперы.

enum class Int: int { };

Int operator + (Int x, Int y) {
    return static_cast<Int>(static_cast<int>(x) + static_cast<int>(y));
}
Siborgium ★★ ()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от victor79

Потому что void(T) это один цельный тип, а в map<T, int> T это параметр. Сложно объяснить проще. Если ошибаюсь, поправьте.

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

Ну не все же такие клоуны как ты, мы крестики умеем в отличии от тебя, и параметры парсить умеем.

#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;
    };
};

void f1(int) {}
void f2(int&) {}

template <class T>
void test(T &&fn) 
{
	using fn_t = decltype(function(fn));
	static_assert(fn_traits<fn_t>::args_cnt == 1);
	static_assert(same_as<typename fn_traits<fn_t>::result_type, void>, "wrong type of function");
	static_assert(same_as<typename fn_traits<fn_t>::arg<0>::type, int>, "wrong type of function");
}

int main() {
	test(f1);         //ok
	test(f2);         //error
	test([](int){});  //ok
}

А вообще автор сам не знает чего хочет, даже задачу сформулировать не может.

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

Раз ты знаешь крестики и клоуном себя не считаешь, ответь на вопрос корректнее меня.

И раз ты знаешь крестики, то, наверное, знаешь, что в крестиках есть перегрузка, и что твое решение сыплется, если я добавлю

struct S{};

void f1(S);
Siborgium ★★ ()
Ответ на: комментарий от Siborgium

Ничего никуда не сыплется. Мы тут решаем непонятно какую задачу. Без конкретных требований к желаемой функции говорить - лишь воду лить. Он в первом посте хотел получать функцию, которая берет функтор возвращающий void и принимающий любой одни тип по значению, можно закомментить последний статитк ассерт в моем примере - это будет ровно оно (вставил просто для показа, что парсить парметры функции можно, ты намекал на какую-то непреодалимую сложность). Я всё.

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

ы намекал на какую-то непреодалимую сложность

«непреодолимую». Я отвечал на вопрос о том, почему не выводится параметр T.

Мы тут решаем непонятно какую задачу.

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

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

ТС хочет, чтобы нельзя было передать произвольную функцию на вход, а можно было исключительно с задекларированными типами аргументов и возвращаемого значения, потому что боится неявных приведений типов.

Вообще-то он приниает функтор с шаблонным параметром в качестве аргумента, о каких приведениях ты толкуешь? Туда изначально все проскочет.

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

функтор

Который рекомендуют называть функциональным объектом.

Туда изначально все проскочет

Для ТС это и является проблемой, потому что передает он туда не «все», а конкретные типы переменных.

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

Для ТС это и является проблемой, потому что передает он туда не «все», а конкретные типы переменных.

Так я же ему сразу сделал проверку на то, является ли параметр функции конкретным типом (сделал проверку - является переданный функтор void(int) - подставить любое желаемое). Шаблон не хочет автоматически выводить тип, как бы его заставить? (комментарий) А он в ответ полез вставлять туда шаблонный параметр. Повторю - он сам не понимает задачу.

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

Потому что void(T) это один цельный тип, а в map<T, int> T это параметр. Сложно объяснить проще. Если ошибаюсь, поправьте.

Это не объясняет. Насколько я знаю, в с++ нет понятия цельного типа, есть примитивные типы. map<T,int> настолько же цельный или примитивный тип, как и function<void(T)> - оба классы. Можно указывать map<const T,int> и оно по прежнему будет работать.

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

является ли параметр функции конкретным типом

Ты по началу сделал вариант, является ли лямбда определенным типом, а нужно было параметр лямбды определенного типа. Второй вариант с туплами возможно прокатит. Кажется там лучше same_as сразу на тупл - в одну строку.

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

Ты так и не привел примеров твоих логических ошибок, ради которых устроил весь этот цирк.

Там все слишком сложно. pavlick опять начнет объяснять свою непонятливость моим не умением объяснить.

Наверное все свелось к желанию понять, почему при map не нужна специализация, а при function нужна.

victor79 ()