Давайте предположим, что я хочу создать свой собственный лямбда-основанный коммутатор со следующим синтаксисом:
auto s = make_switch(std::pair{0, []{ return 0; }},
std::pair{1, []{ return 50; }},
std::pair{2, []{ return 100; }});
assert( s(0) == 0 );
assert( s(1) == 50 );
assert( s(2) == 100 );
Я хотел бы использовать сложить выражение чтобы иметь краткую реализацию, которая не требует рекурсии. Идея состоит в том, чтобы создать нечто похожее на кучу вложенных if
заявления:
if(x == 0) return 0;
if(x == 1) return 50;
if(x == 2) return 100;
Я хотел бы написать это:
// pseudocode
template <typename... Pairs>
auto make_switch(Pairs... ps)
{
return [=](int x)
{
( if(ps.first == x) return ps.second(), ... );
};
}
Код выше не работает как if(...){...}
это не выражение. Затем я попытался использовать &&
оператор:
template <typename... Pairs>
auto make_switch(Pairs... ps)
{
return [=](int x)
{
return ((ps.first == x && ps.second()), ...);
};
}
Это компилируется, но возвращает результат ps.first == x && ps.second()
, который является bool
а не int
ценность, которую я хочу.
Я хотел бы какой-то оператор, который представляет собой комбинацию между оператор запятой а также &&
: следует оценить и оценить до Правая сторона оператора, если левая сторона оценивает true
,
Я не могу придумать какую-либо технику, которая позволила бы мне реализовать это таким образом, чтобы я мог получить ps.second()
возвращаем значение и распространяем его на вызывающую лямбду, возвращаемую make_switch
,
Возможно ли реализовать этот вид «каскадирования»? if
S «шаблон с сложить выражение? Я хотел бы оценить только столько выражений, сколько требуется, пока не будет найдена соответствующая ветвь.
Я удивлен, что это не было предложено еще:
template <typename ...Pairs> auto make_switch(Pairs ...ps)
{
return [=](int x)
{
int ret;
((x == ps.first && (void(ret = ps.second()), 1)) || ...)
/* || (throw whatever, 1) */ ;
return ret;
};
}
Для этого требуется дополнительная переменная, но, похоже, единственными альтернативами являются рекурсия и класс-обертка с перегруженным двоичным оператором, и оба выглядят для меня менее изящно.
Короткое замыкание ||
используется для остановки функции при обнаружении совпадения.
(Для приведенного выше кода GCC 7.2 дает мне warning: suggest parentheses around '&&' within '||'
, Вероятно, ошибка?)
Вот версия, обобщенная для любых типов: (кредиты @Barry за предложение std::optional
)
template <typename InputType, typename ReturnType, typename ...Pairs> auto make_switch(Pairs ...ps)
{
/* You could do
* using InputType = std::common_type_t<typename Pairs::first_type...>;
* using ReturnType = std::common_type_t<decltype(ps.second())...>;
* instead of using template parameters.
*/
return [=](InputType x)
{
std::optional<ReturnType> ret /* (default_value) */;
( ( x == ps.first && (void(ret.emplace(std::move(ps.second()))), 1) ) || ...)
/* || (throw whatever, 1) */;
return *ret;
};
}
Я решил использовать параметры шаблона для параметров и возвращаемых типов, но вы можете вывести их, если хотите.
Обратите внимание, что если вы решили не иметь значение по умолчанию, ни throw
, затем передача неверного значения на коммутатор даст вам UB.
Это невозможно. Чтобы использовать выражение сгиба, вам нужно определить бинарный оператор на вашем Pairs
,
В вашем случае такой бинарный оператор не может существовать, так как:
Pairs::first
с x
)Более того:
this
в качестве первого аргумента, и вы не могли бы сделать this
указатель на Pairs
или производная от Pairs
;x
,Я думаю, что решение от HolyBlackCat лучше, но … как насчет использования суммы?
template <typename ... Pairs>
auto make_switch (Pairs ... ps)
{
return [=](int x)
{ return ( (ps.first == x ? ps.second() : 0) + ... ); };
}
К сожалению, работает только для типов, где сумма определена.