推导容器和initializer_list-s的模板函数

时间:2018-07-31 04:40:25

标签: c++ templates c++14 c++17 initializer-list

我想编写一个辅助函数,例如:

template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)
{
    using namespace std; // for cbegin/cend and ADL
    return pred(cbegin(left), cend(left), cbegin(right), cend(right));
}

它对容器很有效:

std::vector<int> v1 = {1,2,3,4,5,6};
std::vector<int> v2 = {5,4,3,2,1,6};

std::cout << helper(v1, v2, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;

但无法推断出initializer_list-s(example):

std::cout << helper({1,2,3,4,5,6}, {5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;

是否有惯用的方式来重写helper,以便推论出容器和initializer_list-s?

我想不出比overloads for all combinations of container and initializer_list更好的东西。

4 个答案:

答案 0 :(得分:7)

我认为这里的根本问题是,像{ 1, 2, 3 }这样的 braised-init-list 只是一个 initializer ,而不是{{1 }}。它可以潜在地用于初始化某些给定类型的对象。但这本身不是任何类型的对象。而且,rules for function template argument deduction中似乎没有任何东西可以让您从 braced-init-list 参数中获得std::initializer_list<T>,除非您的函数参数已被声明首先是std::initializer_list<T>

所以我担心写那些重载将是最简单的解决方案……

答案 1 :(得分:1)

这是我能做的最好的事情

template<class X>
struct Range {
  X* container;
  Range(X& x):container(std::addressof(x)) {}
  Range(X&& x):container(std::addressof(x)) {} // dangerous, but hey
  auto begin() const { using std::begin; return begin(*container); }
  auto end() const { using std::end; return end(*container); }
  auto cbegin() const { using std::cbegin; return cbegin(*container); }
  auto cend() const { using std::cend; return cend(*container); }
};

template<class T>
struct Range<std::initializer_list<T>> {
  using X=std::initializer_list<T>;
  X container;
  Range(X x):container(x) {}
  auto begin() const { using std::begin; return begin(container); }
  auto end() const { using std::end; return end(container); }
  auto cbegin() const { using std::cbegin; return cbegin(container); }
  auto cend() const { using std::cend; return cend(container); }
};

template<class T>
Range( std::initializer_list<T> ) -> Range<std::initializer_list< T >>;

template<class C1, class C2>
void foo( Range<C1> r1, Range<C2> c2 ) {}

测试代码:

Range r = {{'a', 'b', 'c'}};
(void)r;
std::vector v = {1,2,3};
foo( Range{{'a','b','c'}}, Range{v} );

您必须手动将参数强制转换为Range才能在调用站点上使用,因为类模板争论推论不适用于函数参数。


我们也许可以采取不同的攻击方式。

template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)

将上述语法更改为链接调用。

helper(v1)({1,2,3})[pred];

将2 ^ n爆炸减少为2。2个重载并没有多大帮助,但仍然...

template<class...Ts>
struct helper_t {
  std::tuple<Ts&&...> containers;
  template<class T>
  helper_t<T, Ts...> operator()(T&& t)&& {
    return { /* append-move containers and t into one tuple */ };
  }
  template<class T>
  helper_t<std::initializer_list<T>, Ts...> operator()(std::initializer_list<T> t)&& {
    return { /* append-move containers and t into one tuple */ };
  }
  template<class F>
  decltype(auto) operator[](F&& f)&& {
    return std::move(*this).apply_impl(
      std::make_index_sequence<sizeof...(Ts)>{},
      std::forward<F>(f)
    );
  }
private:
  template<std::size_t...Is, class F>
  decltype(auto) apply_impl( std::index_sequence<Is...>, F&& f ) && {
    using std::cbegin; using std::cend;
    using std::get;
    return std::forward<F>(f)(
      cbegin( get<Is>(std::move(containers)) ), cend( get<Is>(std::move(containers)) )
    );
  }
};
static constexpr const helper_t<> helper;

我留下了附加元组作为练习。

helper( container1 )( {1,2,3} )( container2 )[ some_lambda ];

是语法。

答案 2 :(得分:0)

请明确指定您要传递的参数是initializer_list,如下所示:-

std::cout << helper(v1, std::initializer_list<int>{5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
std::cout << helper(std::initializer_list<int>{1,2,3,4,5,6}, std::initializer_list<int>{5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;   

这是您可以传递initializer_list的方式,否则您需要对容器和initializer_list的所有组合进行重载

答案 3 :(得分:0)

正如迈克尔·肯泽尔(Michael Kenzel)所解释的那样,问题在于括号初始列表不是std::intializer_list

所以我同意Michael(+1):我看不出有一种方法可以编写一个可以推断出STL容器和(std::initilizer_list<T>)括号初始化列表的单个模板函数。 / p>

但是,如果您可以接受为helper()函数添加helper()帮助函数,则可以使用以下事实:可以将braced-init-list推导出为C样式数组。 / p>

因此,您可以为C样式数组编写一个helper()特定版本,该版本可以转换std::initializer_list(或std::array或...再见)中的C样式数组,或者,在我的示例中,您可以简单地调用原始的helper()函数来传递数组,但可以说明类型。

我的意思是:您可以添加此helper()辅助函数

template <typename F, typename R1, std::size_t S1,
                      typename R2, std::size_t S2>
auto helper (R1 const (&r1)[S1], R2 const (&r2)[S2], F && pred)
 { return helper<F, R1[S1], R2[S2]>(r1, r2, std::forward<F>(pred)); }

缺点:如果两个范围都是STL容器,或者两个范围都是C样式数组(或braced-init-lists),则此方法有效;如果您可以拥有STL容器和C样式的数组,则必须编写另外两个helper()帮助函数:一个用于(仅)第一个范围,一个用于(仅)第二个范围。

以下是一个完整的工作示例

#include <vector>
#include <iostream>
#include <algorithm>

template <typename F, typename Range1, typename Range2>
auto helper (Range1 const & left, Range2 const & right, F && pred)
 { return pred(std::cbegin(left), std::cend(left),
               std::cbegin(right), std::cend(right)); }

template <typename F, typename R1, std::size_t S1,
                      typename R2, std::size_t S2>
auto helper (R1 const (&r1)[S1], R2 const (&r2)[S2], F && pred)
 { return helper<F, R1[S1], R2[S2]>(r1, r2, std::forward<F>(pred)); }

int main ()
 {
   std::vector<int> v1 = {1,2,3,4,5,6};
   std::vector<int> v2 = {5,4,3,2,1,6};

   std::cout << helper(v1, v2,
      [](auto const & ... args){ return std::is_permutation(args...);})
      << std::endl;

   std::cout << helper({1, 2, 3, 4, 5, 6}, {5, 4, 3, 2, 1, 6},
      [](auto const &... args){ return std::is_permutation(args...);})
      << std::endl;
 }