2016-05-27 7 views
9

У меня есть функция, которая step(f):Возвращает результат `f`, если` f` имеет не возвращаемый тип возврата - как реорганизовать этот шаблон?

  1. Выполняет код перед вызовом f.

  2. Звонки f().

  3. Выполняет код после вызова f.

  4. Возвращает f «ы значение результата если f не возвращает void.

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

template <typename TF> 
auto step(TF&& f) 
{ 
    // Execute some actions before `f`. 
    do_something(); 

    using f_return_type = decltype(f()); 
    return static_if(std::is_same<f_return_type, void>{}) 
     .then([](auto&& xf) mutable 
      { 
       // Do not return anything if `xf` returns void. 
       xf(); 

       // Execute some actions after `f`. 
       do_something_after_f(); 
      }) 
     .else_([](auto&& xf) mutable 
      { 
       auto result = xf(); 

       // Execute some actions after `f`. 
       do_something_after_f(); 

       return result; 
      })(std::forward<TF>(f)); 
} 

(Обратите внимание на повторение вызова f и do_something_after_f.)

Мне нужно использовать какое-то условное время компиляции (либо специализация шаблона, либо static_if, как показано на рисунке пример) к ответвлению в зависимости от типа возврата f.

В идеале, я хотел бы, чтобы компилировать:

template <typename TF> 
auto step(TF&& f) 
{ 
    // Execute some actions before `f`. 
    do_something(); 

    decltype(auto) eventual_result = f(); 

    // Execute some actions after `f`. 
    do_something_after_f(); 

    return result; 
} 

Но это не так, потому что eventual_result может быть void, который неполный тип.

Есть ли способ рефакторинга этого кода, чтобы избежать повторения при звонке f() и do_something_after_f()?

+0

Вы ищете 'optional'? Я не могу сказать из вопроса, почему вам нужно что-то вернуть, если вы просто отбросите его. –

+0

@sleeptightpupper. Основная проблема заключается в том, что мы хотим, чтобы функция возвращала любые значения 'f', но поскольку после вызова' f' есть код, его значение должно быть сохранено. Однако вы не можете сохранить значение 'void', что означает, что вам нужны две версии: одна для' f' с возвращаемым типом 'void' и другая для других типов возврата. – chris

+0

Я мог видеть что-то работающее, если вы абстрагируетесь, имея две версии в виде 'void foo (...) {/ * before */return callAndThen (f, [&] {/ * после * /});' – chris

ответ

9

Вы можете выполнить do_something_after_f() после выполнения инструкции return, поместив ее в деструктор локальной переменной.

struct Cleanup { 
    ~Cleanup() { do_something_after_f(); } 
} cleanup; 
return f(); // it's legal to return a void expression in C++ 
+2

Дьявольски умный! – SergeyA

+0

Я считал это, но думал, что захват 'this' приведет к еще большему количеству кода ... с другой стороны, я могу абстрагироваться от функции make_scope_guard (f)', и это будет намного лучше, чем 'static_if 'решение. Вероятно, я буду принимать этот ответ после того, как буду экспериментировать с ним, спасибо! –

+0

Работал отлично! http://i.imgur.com/oe0SwKG.png –

4

Я думаю, что решение Брайана это, так как оно короткое и практичное. Здесь я показываю просто альтернатива

template<typename T> 
struct taken { 
    T &&take() { 
     return std::forward<T>(t); 
    } 

    T &&t; 
}; 

struct take_or_void { 
    void take() { } 
}; 

template<typename T> 
inline taken<T> operator,(T &&t, take_or_void) { 
    return taken<T>{ std::forward<T>(t) }; 
} 

Теперь вы можете записать его в виде

return ([](auto t) -> decltype(auto) { 
    do_something_after_f(); 
    return t.take(); 
})((f(), take_or_void{})); 
+0

Вы заслуживаете +1 для перегрузки 'operator,'. – skypjack

+0

Не уверен. 'Forward' внутри' operator, 'имеет смысл для вас иметь дело с ссылкой пересылки. Используется ли вместо этого «бесполезный»? Вы на самом деле не собираете ссылку на пересылку, 'T &&' имеет совершенно другой смысл, я ошибаюсь? Я имею в виду, что вы знаете фактический тип 'T', и вы перемещаете его копию в' operator'. Не хватит ли возвращать 't' как есть и переместить его обратно к вызывающему? – skypjack

+0

@skypjack, если 'f' возвращает значение, например' std :: string', 'T &&' будет 'std :: string &&', но 'return t;' будет возвращать значение lvalue, потому что 't' является названной ссылкой rvalue , Это не будет компилироваться, потому что тип возврата - 'std :: string &&'. Фактический тип 'T' может быть либо не ссылочным типом для rvalues, либо ссылочным типом lvalue для lvalues. Я этого не знаю. –

0

Вы можете делегировать вызов функции и присвоить результат к структуре, способной представлять «пустота»:

#include <type_traits> 
#include <utility> 

// call 

template <typename R> 
struct Result 
{ 
    R value; 
    Result(R&& value) : value(value) {} 
    R&& operator()() { return std::move(value); }; 
}; 

template <> 
struct Result<void> 
{ 
    void operator()() {}; 
}; 

template<typename F, typename ...Args> 
inline 
typename std::enable_if<! std::is_same<void, typename std::result_of<F(Args...)>::type>::value, 
    Result<typename std::result_of<F(Args...)>::type>>::type 
call(F&& f, Args... args) { 
    return Result<typename std::result_of<F(Args...)>::type>(f(std::forward<Args>(args)...)); 
} 

template<typename F, typename ...Args> 
inline 
typename std::enable_if< std::is_same<void, typename std::result_of<F(Args...)>::type>::value, 
    Result<void>>::type 
call(F&& f, Args... args) { 
    f(std::forward<Args>(args)...); return Result<void>(); 
} 


// test 

template <typename TF, typename ...Args> 
auto step(TF&& f, Args... args) 
{ 
    // ... 
    auto result = call(f, std::forward<Args>(args)...); 
    // ... 
    return result(); 
} 

inline void f() {} 
inline int g(int i) { return i; } 

int main() 
{ 
    // error: ‘void a’ has incomplete type 
    // auto a = step(f); 
    auto b = step(g, 1); 
} 
0

Мне нравится просто обман и принуждение функции всегда возвращают значение:

template <class T> struct tag_type { using type = T; }; 
template <class T> constexpr tag_type<T> tag{}; 

namespace details { 
    template <class F, class... Args> 
    tag_type<void> call_impl(tag_type<void>, F&& f, Args&&... args) { 
     std::forward<F>(f)(std::forward<Args>(args)...); 
     return {}; 
    } 

    template <class R, class F, class... Args> 
    R call_impl(tag_type<R>, F&& f, Args&&... args) { 
     return std::forward<F>(f)(std::forward<Args>(args)...); 
    } 
} 

template <class F, class... Args> 
decltype(auto) call(F&& f, Args&&... args) { 
    return details::call_impl(tag<std::result_of_t<F&&(Args&&...)>>, 
     std::forward<F>(f), std::forward<Args>(args)...); 
} 

Таким образом, call(f, args...) либо дает вам тип возврата правильно, либо экземпляр tag_type<void>. То есть, это всегда объект.

Это позволяет переписать код, как только:

template <typename TF> 
auto step(TF&& f) 
{ 
    // Execute some actions before `f`. 
    do_something(); 

    decltype(auto) eventual_result = call(std::forward<TF>(f)); 

    // Execute some actions after `f`. 
    do_something_after_f(); 

    return result; 
} 

независимо от его типа возвращаемого значения.


выше требует C++ 14, но реализуемо в C++ 11 с дополнительным metafunction вызовом для замены decltype(auto) используя в основном шаблоне call() функции.