Здравствуйте, Chorkov, Вы писали:
C>Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.
C>Сейчас для теста использую следующий код: 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);
}
Здравствуйте, Chorkov, Вы писали:
C>Хочется, перебрать все члены tuple, и вызвать функцию (член класса), если у соответствующего объекта она есть.
C>Сейчас для теста использую следующий код: C>Который копируется для каждого члена, что неудобно.
C>Можно как-то упростить или сделать более читаемым?
Кстати, тут есть один весёлый момент: константность.
Проверка decltype(t.foo(0)) смотрит, чтобы t был неконстантным. Потому что foo — неконстантный метод.
И если кортеж — const или prvalue, то мы будем дефолтиться.
Ну и кстати, можно написать visit так, чтобы не надо было делать дефолтную перегрузку. (Либо передавать дефолтный визитор отдельной функцией).
Но это оставим на потом
, который предложил sergii.p, то все получается гораздо элегантнее — без всяких макросов и метафункций:
Я написал это отдельным комментарием.
Вот только дефолтить значащий аргумент к ... — это моветон.
Поэтому визит и визитёры нужно сделать немножко по-другому. http://rsdn.org/forum/cpp/8501535.1
Здравствуйте, rg45, Вы писали:
R>Так опрятнее, по-моему. И перфект-форвардинг референс в параметре снимает головную боль с контанстностью и lvalue-rvalue.
Это обсуждаемый момент.
Начиная с того, что автор кода может знать ожидаемую семантику foo — как минимум, какая там должна быть константность.
Ну и rvalue кортеж отдаст свои элементы как rvalue reference, которые внутри выражений станут неконстантными. Насколько это то, что нам надо?
Здравствуйте, rg45, Вы писали:
R>И эллипсис ушел, и фейковых параметров не требуется, и пространсва имен не засоряются лишними концептами, и достаточно читабельно, как по мне.
Да, мне тоже нравится. Но нужно понимать, что это не совсем полноценный концепт, в том смысле, что применять его с логическим отрицанием не получится. И если хорошенько присмотреться, то можно увидеть, что этот концепт применяется в комплекте со SFINAE. Тем не менее, этот подход не нарушает никаких правил и может дать ощутимую пользу для множества относительно простых случаев. А для более сложного случая, там, где это действительно нужно, не жалко написать и отдельный полноценный концепт. Ведь оба подхода совершенно свободно можно комбинировать даже в рамках одного выражения.
P.S. Всем казалось, что с приходом концептов SFINAE отомрет, ан нет — концепты лишь расширили арсенал приемов SFINAE.
--
Справедливость выше закона. А человечность выше справедливости.
Здесь реализован паттерн Chain of Responsibility, я правильно понял идею?
P.S. Не покидает ощущение, что это можно реализовать изящнее — с использованием Callable Wrapper, который потом можно будет прогнать через fоld expression с использовалием логического "И". Преимущество должно быть и по внешнему виду кода, и по производительности. Мне кажется, что fold expressions должны лучше поддаваться оптимизации, чем рекурентный проход по туплу. Сам же overloaded при этом остается столь же легковесным, как здесь
Это намеренно так сделано? Не лучше ли было бы в этом случае просто ничего не делать:
void call_(index_t<LAST + 1>, auto&&...) const {} // Пустой обработчик для LAST + 1
Тогда и необходимость в заглушке def(...) отпала бы, и подход работал бы в т.ч. и для пустого множества обработчиков, что расширило бы возможности его применения в различного рода обобщенных операциях.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Еще вот какой у меня вопрос возник: в случае, если подходящий обработчик отсутствует, то вот в этом месте возникнет ошибка компиляции: R>Это намеренно так сделано? Не лучше ли было бы в этом случае просто ничего не делать: R>Тогда и необходимость в заглушке def(...) отпала бы, и подход работал бы в т.ч. и для пустого множества обработчиков, что расширило бы возможности его применения в различного рода обобщенных операциях.
Абсолютно намеренно сделано.
Потому что семантика дефолта пусть будет на откуп пользователю. Втихаря компилироваться и ничего не делать, если ожидалось, что делать всё-таки что-то надо — не самая лучшая стратегия.
А если очень хочется, то в библиотеку добавляешь вот такую функцию
const auto do_nothing = [](auto&&...) {};
// она жеstruct do_nothing_t {
void operator()(auto&&...) const {};
} do_nothing;
и используешь её, вместо двух версий overloaded — с дефолтом и без дефолта
Здравствуйте, 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 при этом остается столь же легковесным, как здесь
overloaded — даже не класс, а простейший шаблон функции!
Единственное отличие — не возникает ошибки компиляции в случае отсутствия обработчика. Вместо этого возвращается false, что означает, что запрос не был обработан. Таким образом, и пустые цепочки обработчиков тоже возможны. В остальном результат в точности как у тебя.
--
Справедливость выше закона. А человечность выше справедливости.
R>Единственное отличие — не возникает ошибки компиляции в случае отсутствия обработчика. Вместо этого возвращается false, что означает, что запрос не был обработан. Таким образом, и пустые цепочки обработчиков тоже возможны. В остальном результат в точности как у тебя.
Хотя для того, чтобы вместо кода результата false получить ошибку компиляции, достаточно просто использовать констрейнт внутри функции overloaded. Следующий пример дает В ТОЧНОСТИ тот же эффект, что и твой:
Здравствуйте, rg45, Вы писали:
К>>Так, хорош. Мы щас допилим до продакшена и будем постить в буст или сразу в комитет?
R>Ну, я уже разогнался, уже легче доехать, чем затормозить
R>Посмотри, как тебе такой вариант:
Твой вариант имеет недостатки:
1) Инстанцируются все перегрузки, которые подошли по формальному признаку "сигнатура well-formed"
Например, функция-заглушка может выглядеть вот так
Или, например, лесенка перегрузок может быть вот такой
[](Foo x, auto y) { ... },
[](Bar x, auto y) { ... }
[](auto x, Buz y) {
// мы тут уверены, что x - ни Foo, ни Barusing X = decltype((x));
static_assert(!is_same_v<X,Foo> && !is_same_v<X,Bar>); // ну или ещё что-то, что ломает компиляцию
},
[](auto x, auto y) { ... }
2) Он жёстко приколочен к тому, что перегрузки возвращают void. Поэтому можно спокойно налепить bool поверх void.
Попробуй возвращать что-нибудь содержательное. Причём, возможно, для разных перегрузок — разное.
Здравствуйте, Кодт, Вы писали:
К>1) Инстанцируются все перегрузки, которые подошли по формальному признаку "сигнатура well-formed"
Согласен, это существенный недостаток.
К>2) Он жёстко приколочен к тому, что перегрузки возвращают void. Поэтому можно спокойно налепить bool поверх void.
Ну, в принципе, реализация не накладывает никаких ограничений на тип результата, он может быть каким угодно. Но результат теряется в моей реализации, это правда.
--
Справедливость выше закона. А человечность выше справедливости.