2017-09-07 83 views
3

在這裏,我目前的模板函數over(vec, f)的兩種變型的首次下調。模板選擇在不違反DRY prinicple

兩個版本迭代一個矢量狀對象,並調用用於每個元件的功能的對象。

一個版本調用帶有兩個參數的函數對象 - 一個元素的參考和索引 - 第二隻用元件參考。

的想法是,讓編譯器來選擇匹配傳入的拉姆達,使用戶可以表達在lambda簽名的意圖,而不必選擇一個不同名稱的免費功能的版本。

下面的代碼:

#include <vector> 
#include <iostream> 

template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 


template<class Vector, class F> 
auto over(Vector &&vec, F &&f) 
-> void_t<decltype(f(vec.operator[](std::declval<std::size_t>()), std::declval<std::size_t>()))> 
{ 
    const auto size = vec.size(); 
    for (std::size_t i = 0; i < size; ++i) { 
     f(vec[i], i); 
    } 
} 


template<class Vector, class F> 
auto over(Vector &&vec, F &&f) 
-> void_t<decltype(f(*vec.begin()))> 
{ 
    for (auto &&x : vec) { 
     f(x); 
    } 
} 

int main() { 
    std::vector<float> vf = {1.0, 1.1, 1.2}; 

    std::cout << "two-argument form:\n"; 
    over(vf, [](auto &&val, auto &&index) { 
     std::cout << index << " : " << val << std::endl; 
    }); 

    std::cout << "\none-argument form:\n"; 
    over(vf, [](auto &&val) { 
     std::cout << val << std::endl; 
    }); 
} 

問:

您將看到void_t<>返回類型發電機內部的子句知道所有的功能的實現。我很不高興這個爲:

一)這是泄漏的實施細則中的接口,

二)它不幹燥。

有沒有更好的方式來實現這一目標,其中:

一)允許改變不改變模板推動者的實施,

B)並不像我的狗有一齣戲,打在我的鍵盤上?

+1

'void_t '將是更簡單的,即使它是不解決你的幹問題。 – Jarod42

+0

@ Jarod42這幾乎是我開始的地方。對於這個微小的算法來說,這沒什麼大不了的,但想象一下20線程。 –

回答

1

在C++ 17,你可以使用SFINAE基於std::is_invocable,類似於:

template <class Vector, class F> 
std::enable_if_t<std::is_invocable<F, 
            typename Vector::value_type, 
            std::size_t>::value> 
over(const Vector& vec, F&& f) 
{ 
    const auto size = vec.size(); 
    for (std::size_t i = 0; i < size; ++i) { 
     f(vec[i], i); 
    } 
} 

template <class Vector, class F> 
std::enable_if_t<std::is_invocable<F, typename Vector::value_type>::value> 
over(const Vector& vec, F&& f) 
{ 
    const auto size = vec.size(); 
    for (const auto& e : vec) { 
     f(e); 
    } 
} 
+1

如果矢量是常量或易失性的,該怎麼辦? –

+0

然後確實,我們需要一個特徵來獲得'decltype(vec [0])'而不必重複。 (但是無論如何,用特性或decltype編寫的代碼大部分都是重複執行:()。 – Jarod42

2

在這個例子中,避免了「重複」將是方式更多的工作/複雜性比重複本身,但基本的想法是計算函數的適應性,然後適當調度。這裏討論一個非常類似的問題:Call function with part of variadic arguments。使用的function_traits執行,你可以可以實現一個函數調用訊(我把它叫做在我回答這個問題FOO):

template<typename F, std::size_t... Is, class Tup> 
void dispatch_impl(F && f, std::index_sequence<Is...>, Tup && tup) { 
    std::forward<F>(f)(std::get<Is>(std::move(tup))...); 
} 

template<typename F, typename... Args> 
void dispatch(F && f, Args&&... args) { 
    dispatch_impl(std::forward<F>(f), 
      std::make_index_sequence<function_traits<F>::arity>{}, 
      std::forward_as_tuple(args...)); 
} 


template<class Vector, class F> 
void over(Vector &&vec, F &&f) 
{ 
    std::size_t i = 0; 
    for (auto &&x : vec) { 
     dispatch(std::forward<F>(f), x, i); 
     ++i; 
    } 
} 

這個答案是14兼容的爲好。現場示例:http://coliru.stacked-crooked.com/a/14750cef6b735d7e

編輯:這種方法不適用於通用lambda表達式。所以另一種方法是以這種方式實施調度:

template<typename F, typename T> 
auto dispatch(F && f, T && t, std::size_t i) -> decltype((std::forward<F>(f)(std::forward<T>(t)),0)) { 
    std::forward<F>(f)(std::forward<T>(t)); 
    return 0; 
} 

template<typename F, typename T> 
auto dispatch(F && f, T && t, std::size_t i) -> decltype((std::forward<F>(f)(std::forward<T>(t), i),0)) { 
    std::forward<F>(f)(std::forward<T>(t),i); 
    return 0; 
} 
+0

嗯,這既好又壞,一方面是DRY,另一方面它需要一個通用的實現。 –

+0

@RichardHodges我想我只是不遵循如何獲得DRY和避免需要共同實現?也許這會幫助你發佈一個更復雜的例子(你的20班),所以我們可以看到哪些部分是真正普遍的,哪些不是。請注意:這種方法不適用於通用lambda表達式。我更喜歡爲lambda表達式指定參數的類型,所以這並不妨礙我,但是我可以在上面看到您使用過'auto'。 –

+0

我的意思是DRY,我們不必在enable_if chicanery(這裏已經實現了這個功能)中重複算法的內部運行,但是代價是失去了切換算法的能力,這取決於提供的la mbda(這是我激勵的例子)。 –

0

好的,這是我第一次認真的嘗試。

有沒有比這更好的東西?

#include <vector> 
#include <iostream> 
#include <string> 

namespace notstd 
{ 
    /* deduce the traits of a container argument, even if it's an rvalue-reference */ 
    template<class T> 
    struct container_traits 
    { 
     static_assert(not std::is_pointer<T>(), ""); 
     using without_reference_type = std::remove_reference_t<T>; 
     using base_type = std::remove_cv_t<without_reference_type>; 

     static constexpr auto is_const = std::is_const<without_reference_type>::value; 
     static constexpr auto is_volaile = std::is_volatile<without_reference_type>::value; 

     using base_value_type = typename base_type::value_type; 
     using value_type = std::conditional_t<is_const, std::add_const_t<base_value_type>, base_value_type>; 
    }; 

    template<class Function, class...Args> 
    struct is_compatible_function 
    { 
     template<class FArg> static auto test(FArg&& f) -> decltype(f(std::declval<Args>()...), void(), std::true_type()); 
     static auto test(...) -> decltype(std::false_type()); 

     static constexpr auto value = decltype(test(std::declval<Function>()))::value; 
    }; 
} 

/** 
* define the 2-argument algorithm, plus provide function compatibility checks 
*/ 
template<class Vector, class Function> 
struct over_op_2 
{ 
    using arg_1_type = std::add_lvalue_reference_t<typename notstd::container_traits<Vector>::value_type>; 
    using arg_2_type = std::size_t; 

    static constexpr auto is_compatible_function = notstd::is_compatible_function<Function, arg_1_type, arg_2_type>::value; 

    template<class VectorArg, class FunctionArg> 
    void operator()(VectorArg&& vec, FunctionArg&& f) const 
    { 
     std::size_t i = 0; 
     for (auto &&x : vec) { 
      f(x, i); 
      ++i; 
     } 
    } 
}; 

/** 
* define the 1-argument algorithm, plus provide function compatibility checks 
*/ 
template<class Vector, class Function> 
struct over_op_1 
{ 
    using arg_1_type = std::add_lvalue_reference_t<typename notstd::container_traits<Vector>::value_type>; 

    static constexpr auto is_compatible_function = notstd::is_compatible_function<Function, arg_1_type>::value; 

    template<class VectorArg, class FunctionArg> 
    void operator()(VectorArg&& vec, FunctionArg&& f) const 
    { 
     for (auto &&x : vec) { 
      f(x); 
     } 
    } 
}; 

/** 
* Choose op_2 if the Function type will allow it, otherwise op_1 if that's possible, otherwise void (error) 
*/ 
template<class Vector, class Function> 
struct select_over_op 
{ 
    using op_1 = over_op_1<Vector, Function>; 
    using op_2 = over_op_2<Vector, Function>; 
    using type = std::conditional_t 
    < 
     op_2::is_compatible_function, 
     op_2, 
     std::conditional_t 
     < 
      op_1::is_compatible_function, 
      op_1, 
      void 
     > 
    >; 

    static_assert(not std::is_same<type, void>(), "function signatures are incompatible"); 
                               ; 
}; 

/** 
* iterate over a vector-like container, calling f(elem, i) if available or f(elem) if not. 
* @param vec is a reference to a vector-like object 
* @param f is a function which is compatible with one of: 
*  void([const]value_type&, std::size_t), or 
*  void([const]value_type&) 
*/ 
template<class Vector, class F> 
decltype(auto) over(Vector &&vec, F &&f) 
{ 
    auto op = typename select_over_op<decltype(vec), decltype(f)>::type(); 
    return op(std::forward<Vector>(vec), std::forward<F>(f));  
} 



int main() { 
    std::vector<double> v{4.1,5.1,6.1}; 
    over(v, [] (auto x, auto y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto const& x, auto const& y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto const& x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 
    over(v, [] (auto x) { std::cout << x << "\n"; }); 
    over(v, [] (auto const& x) { std::cout << x << "\n"; }); 
    over(v, [] (auto && x) { std::cout << x << "\n"; }); 

    // converting to int ok (but meh) 
    over(v, [] (int x) { std::cerr << x << "\n"; }); 

    // converting to string correctly fails 
    // over(v, [] (std::string x) { std::cerr << x << "\n"; }); 

    // const vector... 
    const std::vector<double> vc{4.1,5.1,6.1}; 
    over(vc, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 

    // breaking const contract on the value_type also fails 
    // over(vc, [] (double& x, auto&& y) { std::cout << x << ", " << y << "\n"; }); 

    return 0; 
} 

http://coliru.stacked-crooked.com/a/cab94488736b75ed