2017-09-07 84 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