如何在constexpr上下文调用带ExecutionPolicy的C++标准库算法?
解决方案
1. 显式指定重载类型(直接但繁琐)
对于std::find这类重载函数,直接通过static_cast指定具体的重载版本,让编译器能正确推导模板参数:
#include <algorithm> #include <execution> #include <iterator> template<typename Func, typename... Args> constexpr decltype(auto) unseq_invoke(Func func, Args&&... args) { if constexpr (std::is_constant_evaluated()) { return func(std::forward<Args>(args)...); } else { return func(std::execution::unseq, std::forward<Args>(args)...); } } // 调用示例 int main() { int arr[] = {1,2,3,4,5}; auto begin = std::begin(arr); auto end = std::end(arr); int value = 3; // 用static_cast指定std::find的具体重载 auto result = unseq_invoke( static_cast<int*(*)(int*, int*, const int&)>(std::find), begin, end, value ); }
这种方法无需额外封装,但每次调用都要写冗长的static_cast,适合偶尔使用的场景。
2. 模板变量绑定算法模板(简洁且类型安全)
为每个标准库算法定义对应的模板变量,提前绑定其模板参数,避免重载歧义:
#include <algorithm> #include <execution> #include <iterator> template<typename Func, typename... Args> constexpr decltype(auto) unseq_invoke(Func func, Args&&... args) { if constexpr (std::is_constant_evaluated()) { return func(std::forward<Args>(args)...); } else { return func(std::execution::unseq, std::forward<Args>(args)...); } } // 为std::find定义模板变量 template<typename Iter, typename T> constexpr auto find = std::find<Iter, T>; // 为std::transform定义模板变量 template<typename InIter, typename OutIter, typename UnaryOp> constexpr auto transform = std::transform<InIter, OutIter, UnaryOp>; // 调用示例 int main() { int arr[] = {1,2,3,4,5}; auto begin = std::begin(arr); auto end = std::end(arr); int value = 3; // 直接使用模板变量调用 auto result = unseq_invoke(find<int*, int>, begin, end, value); }
这种方法只需要为常用算法定义一次模板变量,后续调用简洁且类型安全,避免了重载推导问题。
3. 自定义算法包装器(最优雅,适合高频使用)
为每个标准库算法写一个轻量的函数对象包装器,自动处理有无执行策略的重载:
#include <algorithm> #include <execution> #include <iterator> template<typename Func, typename... Args> constexpr decltype(auto) unseq_invoke(Func func, Args&&... args) { if constexpr (std::is_constant_evaluated()) { return func(std::forward<Args>(args)...); } else { return func(std::execution::unseq, std::forward<Args>(args)...); } } // std::find的包装器 struct Find { template<typename Iter, typename T> constexpr auto operator()(Iter first, Iter last, const T& value) const { return std::find(first, last, value); } template<typename Iter, typename T> auto operator()(std::execution::unseq_policy, Iter first, Iter last, const T& value) const { return std::find(std::execution::unseq, first, last, value); } }; // std::transform的包装器 struct Transform { template<typename InIter, typename OutIter, typename UnaryOp> constexpr auto operator()(InIter first, InIter last, OutIter dest, UnaryOp op) const { return std::transform(first, last, dest, op); } template<typename InIter, typename OutIter, typename UnaryOp> auto operator()(std::execution::unseq_policy, InIter first, InIter last, OutIter dest, UnaryOp op) const { return std::transform(std::execution::unseq, first, last, dest, op); } }; // 定义全局常量包装器 constexpr Find find; constexpr Transform transform; // 调用示例 int main() { int arr[] = {1,2,3,4,5}; auto begin = std::begin(arr); auto end = std::end(arr); int value = 3; // 直接调用包装器,无需任何额外语法 auto result = unseq_invoke(find, begin, end, value); }
这种方法最优雅,调用方式和原标准库算法几乎一致,完全避免了重载推导问题,同时保留了constexpr和运行时向量化的特性。唯一的代价是需要为常用算法手动编写包装器,工作量不大且可复用。
4. 模板参数化算法(无需包装器的通用方案)
修改unseq_invoke,让它直接接受算法模板作为参数,而非算法函数本身,通过模板参数推导避开重载问题:
#include <algorithm> #include <execution> #include <iterator> template<template<typename...> typename Alg, typename... AlgParams, typename... Args> constexpr decltype(auto) unseq_invoke(Args&&... args) { if constexpr (std::is_constant_evaluated()) { return Alg<AlgParams...>(std::forward<Args>(args)...); } else { return Alg<AlgParams...>(std::execution::unseq, std::forward<Args>(args)...); } } // 调用示例 int main() { int arr[] = {1,2,3,4,5}; auto begin = std::begin(arr); auto end = std::end(arr); int value = 3; // 指定算法模板和其模板参数,再传调用参数 auto result = unseq_invoke<std::find, int*, int>(begin, end, value); }
这种方法完全不需要包装器或显式类型转换,直接通过模板参数指定算法和其模板参数,调用逻辑清晰,适合对模板语法熟悉的场景。
内容的提问来源于stack exchange,提问作者Benjamin Buch
相关产品推荐
相关产品推荐

