接受模板值参数或函数参数的函数模板
Function template accepting either a template value parameter or a function argument
问题
有没有办法编写接受模板值参数(对于静态已知值)或经典函数参数(对于动态值)的模板化函数?
我会想象一些像这样可调用的东西,允许编译器优化“静态”版本。
static_dynamic_fun<T>(a, b, default_fun<T>); // Use function argument
static_dynamic_fun<T, default_fun<T>>(a, b); // Use template parameter
注意:我们可以使用 C++20(尽管使用 C++11 甚至 C++14 的解决方案对更广泛的受众会有用)。
上下文
我有一个模板函数,其参数之一是静态已知函数:
template<typename T, auto dist = default_fun<T>>
T static_fun(const T* a, const T* b){...}
dist 参数在几个循环中被大量使用(调用数十亿次)- 在模板中使用它允许编译器内联函数(它通常很小)。
我们还需要一个具有动态“dist”函数的代码版本(例如,由 lambda 生成):
template <typename T>
using funtype = std::function<T(const T* a, const T* b)>
template<typename T>
T dynamic_fun(const T* a, const T* b, const funtype<T>& dist = default_fun<T>){...}
问题是我们有重复的代码。我检查了上面的程序集输出:即使默认函数是静态已知的,调用也没有内联。
我试过了,希望编译器能“看到”优化机会,
但无济于事。
template<typename T, auto d = default_fun<T>>
T static_dynamic_fun(const T* a, const T* b, const funtype<T>& dist=d){...}
我跟踪了 ASM 中的调用(“-fverbose-asm”GCC 标志)——因为我们经过了一个 std::function,开销甚至比对函数指针的间接调用更大(当我们使用带有捕获的 lambda 之类的东西时,我们很乐意支付的成本。
# /usr/include/c++/11.1.0/bits/std_function.h:560: return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);
leaq 216(%rsp), %rsi #, tmp490
movq %r12, %rdx # tmp666,
movq %r14, %rdi # tmp650,
call *552(%rsp) # MEM[(struct function *)_922]._M_invoke
** 编辑:添加一个最小的可重现示例 **
Header mre.hpp
定义:
- 函数类型,
funtype
- 该类型的函数,
argfun
argfun
的两个“用户”(请参阅下面的 mre.cpp
了解用法)
static_fun
,通过模板参数使用 argfun
dynamic_fun
,通过函数参数使用 argfun
#include <functional>
template <typename T>
using funtype = std::function<T(const T* a, const T* b)>;
template <typename T>
T argfun(const T*a, const T*b){
T d = a[0] - b[0];
return d*d;
}
template <typename T, auto static_callme>
T static_fun(size_t s, const T* a, const T* b){
T acc=0;
for(size_t i=0; i<s; ++i){
acc+=static_callme(a+i, b+i);
}
return acc;
}
template <typename T>
T dynamic_fun(size_t s, const T* a, const T* b, const funtype<T>& dynamic_callme){
T acc=0;
for(size_t i=0; i<s; ++i){
acc+=dynamic_callme(a+i, b+i);
}
return acc;
}
文件mre.cpp
#include "mre.hpp"
#include <random>
#include <vector>
#include <iostream>
int main(){
// Init random
std::random_device rd;
unsigned seed = rd();
std::mt19937_64 prng(seed);
// Random size for vectors
size_t size = std::uniform_int_distribution<std::size_t>(10, 35)(prng);
// Random vectors a and b of doubles [0, 1[
std::uniform_real_distribution<double> udist{0.0, 1.0};
auto generator = [&prng, &udist]() { return udist(prng); };
std::vector<double> a(size);
std::generate(a.begin(), a.end(), generator);
std::vector<double> b(size);
std::generate(b.begin(), b.end(), generator);
// Static call
double res_sta = static_fun<double, argfun<double>>(size, a.data(), b.data());
std::cout << "size = " << size << " static call = " << res_sta << std::endl;
// Dynamic call
double res_dyn = dynamic_fun(size, a.data(), b.data(), argfun<double>);
std::cout << "size = " << size << " dynamic call = " << res_dyn << std::endl;
// Just to be sure:
if(res_sta != res_dyn){
std::cerr << "Error!" << std::endl;
return 1;
} else {
std::cout << "Ok" << std::endl;
}
return 0;
}
我用
g++ -std=c++20 mre.cpp -O3 -S -fverbose-asm
在 asm 文件 mre.s
中,搜索
static_callme
: 看到对 argfun 的调用是内联的
dynamic_callme
:看到调用经过std::function
我希望在没有代码重复的情况下实现这两种行为。 static_fun
和dynamic_fun
的body是一样的
因为您已经在使用模板,所以只需使用 typename F
而不是 std::function<>
:
template <typename T, typename F>
T static_dynamic_fun(size_t s, const T* a, const T* b, F f)
{
T acc = 0;
for (size_t i = 0; i != s; ++i) {
acc += f(a + i, b + i);
}
return acc;
}
// and possibly, for default
template <typename T>
T static_dynamic_fun(size_t s, const T* a, const T* b)
{
return static_dynamic_fun(s, a, b, default_f<T>{});
}
注意:您可能更喜欢传递 lambda 而不是函数指针,这样更容易内联。
问题
有没有办法编写接受模板值参数(对于静态已知值)或经典函数参数(对于动态值)的模板化函数? 我会想象一些像这样可调用的东西,允许编译器优化“静态”版本。
static_dynamic_fun<T>(a, b, default_fun<T>); // Use function argument
static_dynamic_fun<T, default_fun<T>>(a, b); // Use template parameter
注意:我们可以使用 C++20(尽管使用 C++11 甚至 C++14 的解决方案对更广泛的受众会有用)。
上下文
我有一个模板函数,其参数之一是静态已知函数:
template<typename T, auto dist = default_fun<T>>
T static_fun(const T* a, const T* b){...}
dist 参数在几个循环中被大量使用(调用数十亿次)- 在模板中使用它允许编译器内联函数(它通常很小)。 我们还需要一个具有动态“dist”函数的代码版本(例如,由 lambda 生成):
template <typename T>
using funtype = std::function<T(const T* a, const T* b)>
template<typename T>
T dynamic_fun(const T* a, const T* b, const funtype<T>& dist = default_fun<T>){...}
问题是我们有重复的代码。我检查了上面的程序集输出:即使默认函数是静态已知的,调用也没有内联。
我试过了,希望编译器能“看到”优化机会, 但无济于事。
template<typename T, auto d = default_fun<T>>
T static_dynamic_fun(const T* a, const T* b, const funtype<T>& dist=d){...}
我跟踪了 ASM 中的调用(“-fverbose-asm”GCC 标志)——因为我们经过了一个 std::function,开销甚至比对函数指针的间接调用更大(当我们使用带有捕获的 lambda 之类的东西时,我们很乐意支付的成本。
# /usr/include/c++/11.1.0/bits/std_function.h:560: return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);
leaq 216(%rsp), %rsi #, tmp490
movq %r12, %rdx # tmp666,
movq %r14, %rdi # tmp650,
call *552(%rsp) # MEM[(struct function *)_922]._M_invoke
** 编辑:添加一个最小的可重现示例 **
Header mre.hpp
定义:
- 函数类型,
funtype
- 该类型的函数,
argfun
argfun
的两个“用户”(请参阅下面的mre.cpp
了解用法)static_fun
,通过模板参数使用argfun
dynamic_fun
,通过函数参数使用argfun
#include <functional>
template <typename T>
using funtype = std::function<T(const T* a, const T* b)>;
template <typename T>
T argfun(const T*a, const T*b){
T d = a[0] - b[0];
return d*d;
}
template <typename T, auto static_callme>
T static_fun(size_t s, const T* a, const T* b){
T acc=0;
for(size_t i=0; i<s; ++i){
acc+=static_callme(a+i, b+i);
}
return acc;
}
template <typename T>
T dynamic_fun(size_t s, const T* a, const T* b, const funtype<T>& dynamic_callme){
T acc=0;
for(size_t i=0; i<s; ++i){
acc+=dynamic_callme(a+i, b+i);
}
return acc;
}
文件mre.cpp
#include "mre.hpp"
#include <random>
#include <vector>
#include <iostream>
int main(){
// Init random
std::random_device rd;
unsigned seed = rd();
std::mt19937_64 prng(seed);
// Random size for vectors
size_t size = std::uniform_int_distribution<std::size_t>(10, 35)(prng);
// Random vectors a and b of doubles [0, 1[
std::uniform_real_distribution<double> udist{0.0, 1.0};
auto generator = [&prng, &udist]() { return udist(prng); };
std::vector<double> a(size);
std::generate(a.begin(), a.end(), generator);
std::vector<double> b(size);
std::generate(b.begin(), b.end(), generator);
// Static call
double res_sta = static_fun<double, argfun<double>>(size, a.data(), b.data());
std::cout << "size = " << size << " static call = " << res_sta << std::endl;
// Dynamic call
double res_dyn = dynamic_fun(size, a.data(), b.data(), argfun<double>);
std::cout << "size = " << size << " dynamic call = " << res_dyn << std::endl;
// Just to be sure:
if(res_sta != res_dyn){
std::cerr << "Error!" << std::endl;
return 1;
} else {
std::cout << "Ok" << std::endl;
}
return 0;
}
我用
g++ -std=c++20 mre.cpp -O3 -S -fverbose-asm
在 asm 文件 mre.s
中,搜索
static_callme
: 看到对 argfun 的调用是内联的dynamic_callme
:看到调用经过std::function
我希望在没有代码重复的情况下实现这两种行为。 static_fun
和dynamic_fun
的body是一样的
因为您已经在使用模板,所以只需使用 typename F
而不是 std::function<>
:
template <typename T, typename F>
T static_dynamic_fun(size_t s, const T* a, const T* b, F f)
{
T acc = 0;
for (size_t i = 0; i != s; ++i) {
acc += f(a + i, b + i);
}
return acc;
}
// and possibly, for default
template <typename T>
T static_dynamic_fun(size_t s, const T* a, const T* b)
{
return static_dynamic_fun(s, a, b, default_f<T>{});
}
注意:您可能更喜欢传递 lambda 而不是函数指针,这样更容易内联。