2016-09-23 3 views
4

В CppCon 2015, S. Lavavej из Microsoft said, чтобы избежать использования result_of. Но у меня есть ситуация, когда я не могу найти подходящую альтернативу.Как преобразовать std :: result_of в decltype в аргументе шаблона

Рассмотрите следующий код. Есть ли способ изменить std::result_of<F()>::type вместо decltype?

#include <iostream> 
#include <future> 
#include <type_traits> 

template<typename T> 
struct NopErrCB 
{ 
    constexpr T operator()() const { return T(); } 
}; 

template <typename Func, typename ErrCB> 
struct SafeTaskWrapper { 
    Func f; 
    ErrCB errCB; 

    template <typename... T> 
    auto operator()(T&&... args) -> decltype(f(args...)) { 
    try 
    { 
     return f(std::forward<T>(args)...); 
    } 
    catch (...) 
    { 
     return errCB(); 
    } 
    } 

}; 
//         vvv OVER HERE vvv 
template <typename F> 
SafeTaskWrapper<F, NopErrCB<typename std::result_of<F()>::type>> make_safe_task(F&& f) { 
    return { std::forward<F>(f) }; 
} 

template <typename F, typename ErrCB> 
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb) { 
    return { std::forward<F>(f), std::forward<ErrCB>(cb) }; 
} 

int main() { 
    auto futA = std::async(std::launch::async, make_safe_task([] { throw "A"; return 1; })); 
    auto futB = std::async(std::launch::async, make_safe_task([] { throw "B"; return 1; }, [] { return 2; })); 
    auto futC = std::async(std::launch::async, make_safe_task([] { throw "C"; })); 
    std::cout << futA.get() << std::endl; 
    std::cout << futB.get() << std::endl; 
    futC.get(); 
} 

PS. Пожалуйста, не возражайте против цели SafeTaskWrapper. Я знаю, что будущее уже обрабатывает исключения C++. Это всего лишь демоверсия, фактический код предназначен для обработки исключений Windows SEH, но это не имеет значения для этого вопроса.

+0

'decltype (std :: declval ()())'? – Jarod42

ответ

4

Вы можете использовать

decltype(std::declval<F>()()) 

вместо

typename std::result_of<F()>::type 

, которые похожи, но имеют тонкие различия

+0

Единственные отличия в C++ 14 связаны с указателями на функции-члены или указатели на члены. Других различий нет, и никаких различий в SFINAE не существует. Итак, в чем смысл этого изменения? – Yakk

2

Здесь std::result_of используется. Я думаю, этого достаточно.

Если вы хотите получить более короткий и читаемый код, рассмотрите возможность использования std::result_of_t, который добавляется с C++ 14.

//NopErrCB<typename std::result_of<F()>::type> 
NopErrCB<std::result_of_t<F()>> // equal 
1

Прямой перевод будет:

template <typename F> 
SafeTaskWrapper<F, NopErrCB<decltype(std::declval<F>()())>> make_safe_task(F&& f) { 
    return { std::forward<F>(f) }; 
} 

Который, кажется, не мне яснее.

4

Да, ваше использование result_of здесь вызывает ошибку. И да, есть способ написать свой код без result_of, который очищает ошибку. И мы можем сделать это, не создавая эквивалентную замену багги decltype.

Во-первых, обратите внимание, что у вашего кода result_of есть ошибка.

struct evil { 
    template<class...Args> 
    int operator()(Args&&)&&{return 0;} 
    template<class...Args> 
    std::string operator()(Args&&)const&{return "hello";} 
}; 

Передает evil{} в код, и он не будет компилироваться. Это потому, что вы оценили f в SafeTaskWrapper в контексте lvalue, в то время как вы вычислили result_of в контексте rvalue.

Это точно такая ошибка, что S.T.L. говорил.

Теперь std::result_of_t<F(Args...)> в основном эквивалентно decltype(std::declval<F>()(std::declval<Args>()...)), или в данном случае decltype(std::declval<F>()()). Замена result_of_t на decltype в этом смысле совершенно бесполезна.

Что я буду делать здесь, я бы изменил остальную часть вашего кода.

struct anything_really { 
    template<class T> operator T() const { return {}; } 
}; 
struct return_anything { 
    anything_really operator()() const { return {}; } 
}; 

это заменяет ваш тип errCB. Обратите внимание, что это не тип шаблона.

Далее мы должны решить проблему «не возвращайся в пустоту».Это делает это:

template<class R> 
struct smart_invoke { 
    template<class F> 
    R operator()(F&& f)const { 
    return std::forward<F>(f)(); 
    } 
}; 
template<> 
struct smart_invoke<void> { 
    template<class F> 
    void operator()(F&& f)const { 
    std::forward<F>(f)(); 
    } 
}; 

Который дает нам наше окончательное решение:

template <typename Func, typename ErrCB=return_anything> 
struct SafeTaskWrapper { 
    Func f; 
    ErrCB errCB; 

    template <class... Ts> 
    decltype(auto) operator()(Ts&&... args) { 
    try { 
     return f(std::forward<T>(args)...); 
    } catch (...) { 
     using R=decltype(f(std::forward<T>(args)...)); 
     return smart_invoke<R>{}(errCB); 
    } 
    } 
}; 

Теперь наши фабричные функции. Я мог бы сохранить два, но я ленив.

template <class ErrCB=return_anything, class F> 
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb = {}) { 
    return { std::forward<F>(f), std::forward<ErrCB>(cb) }; 
} 

Я также поменял порядок аргументов шаблона, так что вы можете передать в другой по умолчанию, конструктивны ErrCB и не беспокоить, переходящий в значении, когда я был у него.

И result_of_t был ликвидирован.

Также в вашем возвратном декламате возвращались ошибки. Он должен быть точно так же, как и оператор возврата (ключа), не похожий.


Есть исключения, если F является указателем на функцию-член или член или тому подобное, но ваш код не будет работать в тех случаях, так или иначе. В C++ 17 вы должны будете позвонить std::invoke в свой operator() и в предложение типа возврата decltype. Остальные изменения остаются правильными, и поскольку мы избегаем выведения типа возврата в двух местах, они остаются неизменными.

+0

Спасибо за информацию. Хорошая идея с функтором, который возвращает любой тип, который вы ожидаете. Но я получаю: 'error C2562: '... operator(): функция' void 'возвращает значение' в строке, где я создаю 'futC' (void lambda). – rustyx

+0

@RustyX исправлено: я написал 'smart_invoke', который знает, чтобы отменить возвращаемые значения, если ожидается, что он вернет' void'. Обратите внимание, что у вас была бы такая же проблема, если бы кто-то вручную передал 'errCB', который не возвращал' void' и 'f', которые вернули void. – Yakk

+0

owww, что 'smart_invoke' действительно выбрасывает ключ в решение :) И я думаю, что вам не хватает' template <> 'перед' 'специализация. – rustyx

Смежные вопросы