Можно ли записать читабельнее?
От: Chorkov Россия  
Дата: 04.04.23 08:30
Оценка:
Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.

Сейчас для теста использую следующий код:
template<typename T>
static auto foo_if_possible(T& worker, int x)
    -> decltype( worker.foo(x) )
{
    return worker.foo(x);
}
template<typename T>
static void foo_if_possible(T& worker,...)  {}

Который копируется для каждого члена, что неудобно.

Можно как-то упростить или сделать более читаемым?

  Компилируемый пример
https://godbolt.org/z/e4Y51f3zP

#include <mutex>
#include <algorithm>
#include <execution>
#include <iostream>

using namespace std;

struct A {
   void foo(int) { std::cout<<"A::foo"<<std::endl; }
} a;
struct B {
   void bar(const char*) { std::cout<<"B::bar"<<std::endl; }
} b;
struct C {
} c;

auto workers() { 
    return std::tie(a,b,c);  // Тут длинный список
}

template<typename T>
static auto foo_if_possible(T& worker, int x)
    -> decltype( worker.foo(x) )
{
    return worker.foo(x);
}
template<typename T>
static void foo_if_possible(T& worker,...)  {}

template<typename T>
static auto bar_if_possible(T& worker, const char* str)
    -> decltype( worker.bar(str) )
{
    return worker.bar(str);
}
template<typename T>
static void bar_if_possible(T& worker,...)  {}


void foo_all(int x) {
    auto visitor = [x](auto& woker){ foo_if_possible(woker, x); };
    std::apply([visitor](auto...x){(visitor(x),...);}, workers());
}

void bar_all(const char* str) {
    auto visitor = [str](auto& woker){ bar_if_possible(woker,str); };
    std::apply([visitor](auto...x){(visitor(x),...);}, workers());
}

int main(){
    foo_all(42);
    bar_all("str");
}
Re: Можно ли записать читабельнее?
От: sergii.p  
Дата: 04.04.23 09:09
Оценка: 91 (3)
Здравствуйте, Chorkov, Вы писали:

C>Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.


не понял, что надо, но отвечу Как понял, нужен аналог std::visit для кортежей. Его можно сделать так:

#include <algorithm>
#include <tuple>
#include <iostream>
#include <iomanip>

// определение функции visit
template<typename T, typename V, size_t... I>
void visit_impl(T&& t, V&& v, std::index_sequence<I...>)
{
    (..., v(std::get<I>(t)));
}

template<typename T, typename V>
void visit(T&& t, V&& v)
{
    visit_impl(std::forward<T>(t), std::forward<V>(v),
        std::make_index_sequence<std::tuple_size<
            typename std::decay<T>::type>::value>());
}

// вспомогательный тип для visitor
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main()
{
    auto t = std::make_tuple(100, "500", '!');
    visit(t, overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; } } );
    return 0;
}
Re: Можно ли записать читабельнее?
От: vopl Россия  
Дата: 04.04.23 09:34
Оценка: 8 (1)
Здравствуйте, Chorkov, Вы писали:

C>Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.


C>Сейчас для теста использую следующий код:

C>
C>template<typename T>
C>static auto foo_if_possible(T& worker, int x)
    ->> decltype( worker.foo(x) )
C>{
C>    return worker.foo(x);
C>}
C>template<typename T>
C>static void foo_if_possible(T& worker,...)  {}
C>

C>Который копируется для каждого члена, что неудобно.

C>Можно как-то упростить или сделать более читаемым?


Можно использовать примерно такой же подход как в boost::hana::is_valid, смотри тут https://www.boost.org/doc/libs/1_61_0/libs/hana/doc/html/structboost_1_1hana_1_1type.html#a2d2e7e08e284f7e0bd1bd9c3ad0e0a2b и особенно — смотри пример.
За счет него можно будет определять наличие метода прямо в коде, а не во внешнем SFINAE-тесте. Затем, тут же в коде — использовать его в "if constexpr", внутри которого и вызывать метод. Таким образом, схлопнутся внешние детекторы-вызываторы foo_if_possible/bar_if_possible. Примерно так:
#include <boost/hana/assert.hpp>
#include <boost/hana/not.hpp>
#include <boost/hana/type.hpp>
#include <iostream>
#include <tuple>

namespace hana = boost::hana;

struct A {
   void foo(int x) { std::cout<<"A::foo("<<x<<")"<<std::endl; }
} a;
struct B {
   void bar(const char* x) { std::cout<<"B::bar("<<x<<")"<<std::endl; }
} b;
struct C {
} c;

constexpr auto workers(){
        return std::tie(a,b,c);  // Тут длинный список
}

//////////////////////////////////////////////////////
void foo_all(int x) {
    auto visitor = [x](auto woker)
    {
        if constexpr(hana::is_valid([](auto f) -> decltype((void)f.foo(x)) { })(woker))
            woker.foo(x);
    };
    std::apply([&](auto&&...x){(visitor(x),...);}, workers());
}

int main()
{
    foo_all(42);
}
Re[2]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 06.04.23 22:55
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>
SP>// вспомогательный тип для visitor
SP>template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
SP>template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
SP>


Бомба!

P.S. Я как-то провтыкал тот момент, когда агрегатная инициализация стала разрешена для базовых классов. Полезная штука.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 06.04.2023 23:03 rg45 . Предыдущая версия .
Re: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 07.04.23 19:59
Оценка:
Здравствуйте, Chorkov, Вы писали:

C>Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.


C>Сейчас для теста использую следующий код:

C>Который копируется для каждого члена, что неудобно.

C>Можно как-то упростить или сделать более читаемым?


Ну, можно сделать макрос.
#define DECLARE_HAS_MEMBER(member) \
struct has_##member { \
    template<class T, class = void> struct test : std::false_type {}; \
    template<class T> struct test<T, std::void_t<decltype(&T::member)>> : std::true_type {}; \
}; \
// endmacro

#define HAS_MEMBER(type, member) (has_##member::template test<std::decay_t<type>>::value)

DECLARE_HAS_MEMBER(foo)
DECLARE_HAS_MEMBER(bar)


А функцию-визитёр — лямбдой
visit(your_tuple,
  [](auto&& t) {
    if constexpr (HAS_MEMBER(decltype(t), foo)) { t.foo(123); }
    if constexpr (HAS_MEMBER(decltype(t), bar)) { t.bar("hello"); }
    // можно if-else, можно какую угодно логику
  }
);
Перекуём баги на фичи!
Re: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 07.04.23 20:20
Оценка: 8 (1)
Здравствуйте, Chorkov, Вы писали:

в развитие темы: если С++20 и концепты/констрейны, то

template<class T> concept has_foo = requires (T t) { t.foo(0); };
template<class T> concept has_bar = requires (T t) { t.bar(""); };

....
visit(your_tuple,
  overloaded{
    [](auto t) requires has_foo<std::decay_t<decltype(t)>> { t.foo(123); },
    [](auto t) requires has_bar<std::decay_t<decltype(t)>> { t.bar("hello"); },
    [](auto t) { },  // без констрейнов имеет меньший приоритет, чем с констрейнами
  }
);
Перекуём баги на фичи!
Re[2]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 07.04.23 20:21
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>    (..., v(std::get<I>(t)));


Немножко чистоты рук: оператор запятая может быть перегружен, поэтому тут надо приводить операнд к void. На всякий случай.
Перекуём баги на фичи!
Re[2]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 07.04.23 20:37
Оценка: 9 (1)
Здравствуйте, Кодт, Вы писали:

К>Ну, можно сделать макрос.

К>
К>#define DECLARE_HAS_MEMBER(member) \
К>struct has_##member { \
К>    template<class T, class = void> struct test : std::false_type {}; \
К>    template<class T> struct test<T, std::void_t<decltype(&T::member)>> : std::true_type {}; \
К>}; \
К>// endmacro

К>#define HAS_MEMBER(type, member) (has_##member::template test<std::decay_t<type>>::value)

К>DECLARE_HAS_MEMBER(foo)
К>DECLARE_HAS_MEMBER(bar)
К>


К>А функцию-визитёр — лямбдой

К>
К>visit(your_tuple,
К>  [](auto&& t) {
К>    if constexpr (HAS_MEMBER(decltype(t), foo)) { t.foo(123); }
К>    if constexpr (HAS_MEMBER(decltype(t), bar)) { t.bar("hello"); }
К>    // можно if-else, можно какую угодно логику
К>  }
К>);
К>



А вот если взять за основу подход
Автор: sergii.p
Дата: 04.04.23
, который предложил sergii.p, то все получается гораздо элегантнее — без всяких макросов и метафункций:

http://coliru.stacked-crooked.com/a/765467d6116e090e

#include <tuple>
#include <iostream>

// определение функции visit
template<typename T, typename V, size_t... I>
void visit_impl(T&& t, V&& v, std::index_sequence<I...>)
{
    (..., v(std::get<I>(t)));
}

template<typename T, typename V>
void visit(T&& t, V&& v)
{
    visit_impl(std::forward<T>(t), std::forward<V>(v),
        std::make_index_sequence<std::tuple_size<
            typename std::decay<T>::type>::value>());
}

// вспомогательный тип для visitor
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;


/////////////////////////////////////////////////////////////////////////////
// Test case

struct HasFoo
{
    void foo() const { std::cout << "HasFoo::foo()" << std::endl; }
};

struct HasBar
{
    void bar() const { std::cout << "HasBar::bar" << std::endl; }
};

struct HasNothing
{
};

int main()
{
    visit(std::make_tuple(HasFoo{}, HasBar{}, HasNothing{}),
        overloaded {
            [](auto&& t) -> decltype(t.foo()) { return t.foo(); },
            [](auto&& t) -> decltype(t.bar()) { return t.bar(); },
            [](...){ std::cout << "Has neither foo nor bar" << std::endl; }
        }
    );
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 07.04.23 20:48
Оценка:
Здравствуйте, Chorkov, Вы писали:

без концептов — придётся немножко помусорить в коде (фиктивный аргумент)
// visit(f, tuple{a,b,c}, d, e, f) = f(a, d,e,f), f(b, d,e,f), f(c, d,e,f)

template<class F, size_t... Ixs, class T, class... Args>
void visit_(F&& f, std::index_sequence<Ixs...>, T&& t, Args&&... args) {
    ( (void)f(std::get<Ixs>(std::forward<T>(t)), std::forward<Args>(args)...) , ...);
}

template<class F, class T, class... Args>
void visit(F&& f, T&& t, Args&&... args) {
    visit_(
        std::forward<F>(f),
        std::make_index_sequence<std::tuple_size_v<std::decay_t<T>>>(),
        std::forward<T>(t),
        std::forward<Args>(args)...);
}

template<class... Fs> struct overloaded : Fs... { using Fs::operator()...; };
template<class... Fs> overloaded(Fs...) -> overloaded<Fs...>;

int main() {
    overloaded f = {
        [](auto& t, int) -> std::void_t<decltype(t.foo(0))> { t.foo(0); },
        [](auto& t, int) -> std::void_t<decltype(t.bar(""))> { t.bar(""); },
        [](auto& t, ...) { std::cout << __PRETTY_FUNCTION__ << std::endl; },
    };
    std::tuple abc = {A{}, B{}, C{}};
    visit(f, abc, 0);
}


Кстати, тут есть один весёлый момент: константность.
Проверка decltype(t.foo(0)) смотрит, чтобы t был неконстантным. Потому что foo — неконстантный метод.
И если кортеж — const или prvalue, то мы будем дефолтиться.

Ну и кстати, можно написать visit так, чтобы не надо было делать дефолтную перегрузку. (Либо передавать дефолтный визитор отдельной функцией).
Но это оставим на потом
Перекуём баги на фичи!
Re[3]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 07.04.23 20:50
Оценка:
Здравствуйте, rg45, Вы писали:

R>А вот если взять за основу подход
Автор: sergii.p
Дата: 04.04.23
, который предложил sergii.p, то все получается гораздо элегантнее — без всяких макросов и метафункций:


Я написал это отдельным комментарием.

Вот только дефолтить значащий аргумент к ... — это моветон.
Поэтому визит и визитёры нужно сделать немножко по-другому.
http://rsdn.org/forum/cpp/8501535.1
Автор: Кодт
Дата: 07.04.23
Перекуём баги на фичи!
Re[4]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 07.04.23 20:59
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вот только дефолтить значащий аргумент к ... — это моветон.


Тут согласен. Я просто старался упрощать, как мог

К>Поэтому визит и визитёры нужно сделать немножко по-другому.

К>http://rsdn.org/forum/cpp/8501535.1
Автор: Кодт
Дата: 07.04.23


А вот тут, я бы вместо

[](auto& t, int) -> std::void_t<decltype(t.foo(0))> { t.foo(0); }


написал бы все-же вот так:

[](auto&& t, int) -> decltype(t.foo(0)) { return t.foo(0); }


Так опрятнее, по-моему. И перфект-форвардинг референс в параметре снимает головную боль с контанстностью и lvalue-rvalue.

Но с концептами констрейнтами лучше всего, конечно, это очевидно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 07.04.2023 21:32 rg45 . Предыдущая версия . Еще …
Отредактировано 07.04.2023 21:01 rg45 . Предыдущая версия .
Re[2]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 08.04.23 11:35
Оценка: 44 (2)
Здравствуйте, Кодт, Вы писали:

К>в развитие темы: если С++20 и концепты/констрейны, то

К>

К>template<class T> concept has_foo = requires (T t) { t.foo(0); };
К>template<class T> concept has_bar = requires (T t) { t.bar(""); };

К>....
К>visit(your_tuple,
К>  overloaded{
К>    [](auto t) requires has_foo<std::decay_t<decltype(t)>> { t.foo(123); },
К>    [](auto t) requires has_bar<std::decay_t<decltype(t)>> { t.bar("hello"); },
К>    [](auto t) { },  // без констрейнов имеет меньший приоритет, чем с констрейнами
К>  }
К>);
К>


Здесь можно обойтись одним общим концептом и избавиться от необходимости определять концепты для каждого имени в отдельности:

http://coliru.stacked-crooked.com/a/ef1cd277644570b2

template <typename T> concept available = std::is_same_v<T, T>;

int main()
{
    visit(std::make_tuple(HasFoo{}, HasBar{}, HasNothing{}),
        overloaded {
            [](auto&& t) requires available<decltype(t.foo())> { t.foo(); },
            [](auto&& t) requires available<decltype(t.bar())> { t.bar(); },
            [](auto&& t) { std::cout << "Has neither foo nor bar" << std::endl; }
        }
    );
}


И эллипсис ушел, и фейковых параметров не требуется, и пространсва имен не засоряются лишними концептами, и достаточно читабельно, как по мне.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 08.04.2023 20:13 rg45 . Предыдущая версия . Еще …
Отредактировано 08.04.2023 17:26 rg45 . Предыдущая версия .
Re[5]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 08.04.23 22:38
Оценка:
Здравствуйте, rg45, Вы писали:

R>Так опрятнее, по-моему. И перфект-форвардинг референс в параметре снимает головную боль с контанстностью и lvalue-rvalue.


Это обсуждаемый момент.
Начиная с того, что автор кода может знать ожидаемую семантику foo — как минимум, какая там должна быть константность.

Ну и rvalue кортеж отдаст свои элементы как rvalue reference, которые внутри выражений станут неконстантными. Насколько это то, что нам надо?

Можно для надёжности обмазать форвардом:
#define FWD(arg) (std::forward<decltype(arg)>(arg))  // кстати, удобная и полезная конструкция

[](auto&& t, int) -> decltype(FWD(t).foo(0)) { return FWD(t).foo(0); }


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

Мы даже вот такое можем сделать
#define TRY_EVAL(expr)  [](auto&& t, int) -> decltype(expr) { (expr); }


И ещё можно подумать, как сделать overloaded со строгим порядком предпочтений.

https://gcc.godbolt.org/z/4rrx4n1xc

#include <cstdio>
#include <type_traits>
#include <utility>
#include <tuple>

#define FWD(v) std::forward<decltype(v)>(v)

#define EXPLICITLY(expr) -> std::type_identity_t<decltype(expr)> { return expr; }
#define IMPLICITLY(expr) { return expr; }

template<size_t N> using index_t = std::integral_constant<size_t, N>;

template<class... Funs>
struct overloaded {
    std::tuple<Funs...> funs;
    overloaded(const Funs&... funs) : funs{funs...} {}

    static_assert(sizeof...(Funs) != 0);
    static constexpr size_t LAST = sizeof...(Funs) - 1;
    
    template<size_t I>
    auto call_(int, index_t<I> index, auto&&... x) const  // int предпочтительнее, чем long, но делаем SFINAE
    EXPLICITLY(std::get<I>(funs)(FWD(x)...))

    template<size_t I>
    decltype(auto) call_(long, index_t<I> index, auto&&... x) const
    IMPLICITLY(call_(0, index_t<I+1>{}, FWD(x)...))
    
    decltype(auto) call_(long, index_t<LAST> index, auto&&... x) const
    IMPLICITLY(std::get<index.value>(funs)(FWD(x)...))

    decltype(auto) operator()(auto&&... x) const
    IMPLICITLY(call_(0, index_t<0>{}, FWD(x)...))
};
template<class... Funs> overloaded(Funs...) -> overloaded<Funs...>;

void foo(int x) { printf("foo(%d)\n", x); }
void bar(const char* x) { printf("bar(\"%s\")\n", x); }
void buz(const void* x) { printf("buz(%p)\n", x); }
void def(...) { printf("def(...)\n"); }

int main() {
    overloaded o {&foo, &bar, &buz, &def };

    printf("-----use:-----\n");
    o(123);
    o("xxx");
    o(&o);
    
    printf("-----misuse:-----\n");
    o(123.45);   // foo(123)
    o(nullptr);  // bar("(null)")
    
    printf("-----default:-----\n");
    o(&foo);
    o();
    o(1, 2, 3);
}
Перекуём баги на фичи!
Re[6]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 08.04.23 22:59
Оценка: +1
Кстати да, трюк с концептом available выглядит изящно!

https://gcc.godbolt.org/z/W6PW6j1nb
Перекуём баги на фичи!
Re[3]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 08.04.23 23:01
Оценка: 10 (1)
Здравствуйте, rg45, Вы писали:

R>И эллипсис ушел, и фейковых параметров не требуется, и пространсва имен не засоряются лишними концептами, и достаточно читабельно, как по мне.


Симпатично, да.
Переписал свой последовательный overloaded на нём.
https://gcc.godbolt.org/z/W6PW6j1nb
Перекуём баги на фичи!
Re[7]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 09.04.23 11:38
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Кстати да, трюк с концептом available выглядит изящно!


К>https://gcc.godbolt.org/z/W6PW6j1nb


Да, мне тоже нравится. Но нужно понимать, что это не совсем полноценный концепт, в том смысле, что применять его с логическим отрицанием не получится. И если хорошенько присмотреться, то можно увидеть, что этот концепт применяется в комплекте со SFINAE. Тем не менее, этот подход не нарушает никаких правил и может дать ощутимую пользу для множества относительно простых случаев. А для более сложного случая, там, где это действительно нужно, не жалко написать и отдельный полноценный концепт. Ведь оба подхода совершенно свободно можно комбинировать даже в рамках одного выражения.

P.S. Всем казалось, что с приходом концептов SFINAE отомрет, ан нет — концепты лишь расширили арсенал приемов SFINAE.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 09.04.2023 12:39 rg45 . Предыдущая версия . Еще …
Отредактировано 09.04.2023 12:32 rg45 . Предыдущая версия .
Отредактировано 09.04.2023 11:51 rg45 . Предыдущая версия .
Отредактировано 09.04.2023 11:48 rg45 . Предыдущая версия .
Re[4]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 10.04.23 10:57
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Симпатично, да.

К>Переписал свой последовательный overloaded на нём.
К>https://gcc.godbolt.org/z/W6PW6j1nb

Здесь реализован паттерн Chain of Responsibility, я правильно понял идею?

P.S. Не покидает ощущение, что это можно реализовать изящнее — с использованием Callable Wrapper, который потом можно будет прогнать через fоld expression с использовалием логического "И". Преимущество должно быть и по внешнему виду кода, и по производительности. Мне кажется, что fold expressions должны лучше поддаваться оптимизации, чем рекурентный проход по туплу. Сам же overloaded при этом остается столь же легковесным, как здесь
Автор: sergii.p
Дата: 04.04.23
.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 10.04.2023 11:10 rg45 . Предыдущая версия . Еще …
Отредактировано 10.04.2023 11:07 rg45 . Предыдущая версия .
Re[4]: Можно ли записать читабельнее?
От: rg45 СССР  
Дата: 10.04.23 12:50
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Переписал свой последовательный overloaded на нём.

К>https://gcc.godbolt.org/z/W6PW6j1nb

Еще вот какой у меня вопрос возник: в случае, если подходящий обработчик отсутствует, то вот в этом месте возникнет ошибка компиляции:

    decltype(auto) call_(index_t<LAST> index, auto&&... x) const IMPLICITLY(std::get<index.value>(funs)(FWD(x)...))


Это намеренно так сделано? Не лучше ли было бы в этом случае просто ничего не делать:

    void call_(index_t<LAST + 1>, auto&&...) const {} // Пустой обработчик для LAST + 1


Тогда и необходимость в заглушке def(...) отпала бы, и подход работал бы в т.ч. и для пустого множества обработчиков, что расширило бы возможности его применения в различного рода обобщенных операциях.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 10.04.2023 13:36 rg45 . Предыдущая версия . Еще …
Отредактировано 10.04.2023 12:59 rg45 . Предыдущая версия .
Отредактировано 10.04.2023 12:54 rg45 . Предыдущая версия .
Отредактировано 10.04.2023 12:54 rg45 . Предыдущая версия .
Отредактировано 10.04.2023 12:52 rg45 . Предыдущая версия .
Отредактировано 10.04.2023 12:52 rg45 . Предыдущая версия .
Re[5]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 10.04.23 13:49
Оценка:
Здравствуйте, rg45, Вы писали:

R>Еще вот какой у меня вопрос возник: в случае, если подходящий обработчик отсутствует, то вот в этом месте возникнет ошибка компиляции:

R>Это намеренно так сделано? Не лучше ли было бы в этом случае просто ничего не делать:
R>Тогда и необходимость в заглушке def(...) отпала бы, и подход работал бы в т.ч. и для пустого множества обработчиков, что расширило бы возможности его применения в различного рода обобщенных операциях.

Абсолютно намеренно сделано.
Потому что семантика дефолта пусть будет на откуп пользователю. Втихаря компилироваться и ничего не делать, если ожидалось, что делать всё-таки что-то надо — не самая лучшая стратегия.

А если очень хочется, то в библиотеку добавляешь вот такую функцию
const auto do_nothing = [](auto&&...) {};
// она же
struct do_nothing_t {
  void operator()(auto&&...) const {};
} do_nothing;

и используешь её, вместо двух версий overloaded — с дефолтом и без дефолта
Перекуём баги на фичи!
Re[5]: Можно ли записать читабельнее?
От: Кодт Россия  
Дата: 10.04.23 14:11
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Здесь реализован паттерн Chain of Responsibility, я правильно понял идею?


Да.

call_(i, ...) =
— f(i)(...) если подходит
— call_(i+1, ...) если не подходит

сall_(n-1, ...) = f(n-1)(...) в любом случае


R>P.S. Не покидает ощущение, что это можно реализовать изящнее — с использованием Callable Wrapper, который потом можно будет прогнать через fоld expression с использовалием логического "И". Преимущество должно быть и по внешнему виду кода, и по производительности. Мне кажется, что fold expressions должны лучше поддаваться оптимизации, чем рекурентный проход по туплу. Сам же overloaded при этом остается столь же легковесным, как здесь
Автор: sergii.p
Дата: 04.04.23
.


Ой, не уверен, что код будет красивее.
Можно, конечно, соорудить функцию, которая резолвит индекс
https://gcc.godbolt.org/z/n6zvqh364
    static constexpr size_t L = sizeof...(Funs) - 1;

    template<size_t I>
    constexpr auto find_overload(index_t<I>, auto&&... args) const requires available<decltype(std::get<I>(funs)(FWD(args)...))> { return index_t<I>{}; }

    template<size_t I>
    constexpr auto find_overload(index_t<I>, auto&&... args) const { return find_overload(index_t<I+1>{}, FWD(args)...); }

    constexpr auto find_overload(index_t<L>, auto&&... args) const requires available<decltype(std::get<L>(funs)(FWD(args)...))> { return index_t<L>{}; }
    constexpr void find_overload(index_t<L>, auto&&... args) const {}

    decltype(auto) operator()(auto&&... args) const {
        using i_type = decltype(find_overload(index_t<0>{}, args...));
        static_assert(!std::is_same_v<i_type, void>, "no function fits to args");
        return std::get<i_type::value>(funs)(FWD(args)...);
    }


Так, хорош. Мы щас допилим до продакшена и будем постить в буст или сразу в комитет?
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.