推导容器和 initializer_list-s 的模板函数
template function that deduces both containers and initializer_list-s
我想编写一个辅助函数,例如:
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
更好的了。
我认为这里的根本问题是像 { 1, 2, 3 }
这样的 braced-init-list 只是一个 initializer 并且不是 std::initializer_list<T>
类型的对象。它可以潜在地用于初始化某个给定类型的对象。但它本身不是任何类型的对象。 rules for function template argument deduction 中似乎没有任何东西可以让你从 braced-init-list 参数中得到 std::initializer_list<T>
,除非你的函数参数已经被宣布为某种 std::initializer_list<T>
开头。
所以恐怕编写这些重载将是最简单的解决方案……
请明确指定您传递的参数是 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
的所有组合重载
正如 Michael Kenzel 所解释的,问题在于支撑初始化列表不是 std::intializer_list
。
所以我同意 Michael (+1) 的观点:我看不出有什么方法可以编写一个模板函数来推导 STL 容器和(如 std::initilizer_list<T>
)支撑初始化列表.
但是如果你可以接受为你的 helper()
函数添加一个 helper()
辅助函数,你可以使用这样一个事实,即花括号初始化列表可以推导为 C 风格的数组.
所以你可以为 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 样式数组(或花括号初始化列表),则此方法有效;如果你可以有一个 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;
}
这是我能做的最好的:
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
才能在调用站点工作,因为 class 模板论证推导不适用于函数参数。
我们或许可以用不同的方式攻击它。
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 ];
是语法。
我想编写一个辅助函数,例如:
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
更好的了。
我认为这里的根本问题是像 { 1, 2, 3 }
这样的 braced-init-list 只是一个 initializer 并且不是 std::initializer_list<T>
类型的对象。它可以潜在地用于初始化某个给定类型的对象。但它本身不是任何类型的对象。 rules for function template argument deduction 中似乎没有任何东西可以让你从 braced-init-list 参数中得到 std::initializer_list<T>
,除非你的函数参数已经被宣布为某种 std::initializer_list<T>
开头。
所以恐怕编写这些重载将是最简单的解决方案……
请明确指定您传递的参数是 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
的所有组合重载正如 Michael Kenzel 所解释的,问题在于支撑初始化列表不是 std::intializer_list
。
所以我同意 Michael (+1) 的观点:我看不出有什么方法可以编写一个模板函数来推导 STL 容器和(如 std::initilizer_list<T>
)支撑初始化列表.
但是如果你可以接受为你的 helper()
函数添加一个 helper()
辅助函数,你可以使用这样一个事实,即花括号初始化列表可以推导为 C 风格的数组.
所以你可以为 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 样式数组(或花括号初始化列表),则此方法有效;如果你可以有一个 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;
}
这是我能做的最好的:
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
才能在调用站点工作,因为 class 模板论证推导不适用于函数参数。
我们或许可以用不同的方式攻击它。
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 ];
是语法。