В случае, если вам нужно использовать result
(в не пустых случаях) в «некотором родовом материале», я предлагаю решение на основе if constexpr
(поэтому, к сожалению, не до C ++ 17).
Не совсем элегантно, если честно.
Прежде всего, определите "истинный тип возврата" f
(с учетом аргументов)
using TR_t = std::invoke_result_t<F, As...>;
Далее a constexpr
переменная, чтобы увидеть, является ли возвращаемый тип void
(просто, чтобы немного упростить следующий код)
constexpr bool isVoidTR { std::is_same_v<TR_t, void> };
Теперь мы определяем (потенциально) «поддельный тип возврата»: int
когда истиннотип возвращаемого значения void
, TR_t
в противном случае
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Затем мы определяем умный указатель на значение результата как указатель на «ложный тип возврата» (так что int
в пустом случае)
std::unique_ptr<FR_t> pResult;
Проходя через указатель, вместо простой переменной типа «поддельный тип возврата», мы можем работать также, когда TR_t
не является конструируемым по умолчанию или не присваиваемым (пределы, указанные Барри (спасибо), первой версии этого ответа).
Теперь, используя if constexpr
, два случая для исполнения f
(это, ИМХО, самая уродливая часть, потому что мы должны написать два раза один и тот же f
вызов)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_t{std::forward<F>(f)(std::forward<As>(args)...)});
После этого "некоторые общие вещи", которые могут использовать result
(в неисключая случаи), а также `isVoidTR).
В заключение, еще один if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
Как указал Барри, у этого решения есть некоторые важные недостатки, потому что (не void
случаи)
- требуют выделения
- требуют дополнительной копии в соответствии с
return
- вообще не работает, если
TR_t
(типf()
) возвращает тип ссылки
В любом случае, ниже приведен пример полной компиляции C ++ 17
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
{
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR { std::is_same_v<TR_t, void> };
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_t{std::forward<F>(f)(std::forward<As>(args)...)});
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
}
int main ()
{
foo([](){});
//auto a { foo([](){}) }; // compilation error: foo() is void
auto b { foo([](auto a0, auto...){ return a0; }, 1, 2l, 3ll) };
static_assert( std::is_same_v<decltype(b), int> );
}