簡體   English   中英

作為模板參數傳遞的函數

[英]Function passed as template argument

我正在尋找涉及將 C++ 模板函數作為參數傳遞的規則。

這由 C++ 支持,如此處的示例所示:

#include <iostream>

void add1(int &v)
{
  v+=1;
}

void add2(int &v)
{
  v+=2;
}

template <void (*T)(int &)>
void doOperation()
{
  int temp=0;
  T(temp);
  std::cout << "Result is " << temp << std::endl;
}

int main()
{
  doOperation<add1>();
  doOperation<add2>();
}

然而,學習這項技術是困難的。谷歌搜索“函數作為模板參數”並不會產生太多結果。 令人驚訝的是,經典的C++ Templates The Complete Guide也沒有討論它(至少不是我的搜索)。

我的問題是這是否是有效的 C++(或只是一些廣泛支持的擴展)。

另外,在這種模板調用期間,有沒有辦法允許具有相同簽名的函子與顯式函數互換使用?

下列工作在上面的程序,至少在視覺C ++ ,因為語法顯然是錯誤的。 能夠為函子切換函數,反之亦然,這很好,類似於如果要定義自定義比較操作,可以將函數指針或函子傳遞給 std::sort 算法的方式。

   struct add3 {
      void operator() (int &v) {v+=3;}
   };
...

    doOperation<add3>();

指向一兩個 Web 鏈接或 C++ 模板書中的頁面的指針將不勝感激!

是的,它是有效的。

至於讓它也與函子一起工作,通常的解決方案是這樣的:

template <typename F>
void doOperation(F f)
{
  int temp=0;
  f(temp);
  std::cout << "Result is " << temp << std::endl;
}

現在可以稱為:

doOperation(add2);
doOperation(add3());

現場觀看

這樣做的問題是,如果編譯器內聯對add2的調用變得棘手,因為編譯器只知道一個函數指針類型void (*)(int &)正在傳遞給doOperation (但是add3作為一個函子,可以很容易地內聯。這里,編譯器知道將add3類型的對象傳遞給函數,這意味着要調用的函數是add3::operator() ,而不僅僅是一些未知的函數指針。)

模板參數可以按類型(typename T)或按值(int X)參數化。

對一段代碼進行模板化的“傳統”C++ 方法是使用函子——也就是說,代碼在一個對象中,因此該對象賦予代碼唯一的類型。

當使用傳統函數時,這種技術效果不佳,因為類型的變化並不表示特定的函數——而是僅指定許多可能函數的簽名。 所以:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b);
}
int add(int a, int b) { return a + b; }
...

int c = do_op(4,5,add);

不等同於函子的情況。 在這個例子中,do_op 為所有簽名為 int X (int, int) 的函數指針實例化。 編譯器必須非常積極地完全內聯這種情況。 (不過我不排除它,因為編譯器優化已經非常先進。)

判斷這段代碼沒有完全按照我們的要求執行的一種方法是:

int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);

仍然是合法的,顯然這不會被內聯。 要獲得完整的內聯,我們需要按值進行模板化,因此該函數在模板中完全可用。

typedef int(*binary_int_op)(int, int); // signature for all valid template params
template<binary_int_op op>
int do_op(int a, int b)
{
 return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op<add>(4,5);

在這種情況下,每個實例化版本的 do_op 都使用一個特定的可用函數進行實例化。 因此,我們希望 do_op 的代碼看起來很像“return a + b”。 (Lisp 程序員,別傻笑了!)

我們還可以確認這更接近我們想要的,因為:

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);

將無法編譯。 GCC 說:“錯誤:‘func_ptr’不能出現在常量表達式中。換句話說,我不能完全擴展 do_op,因為你在編譯時沒有給我足夠的信息來知道我們的 op 是什么。

因此,如果第二個示例確實完全內聯了我們的操作,而第一個示例不是,那么模板有什么用? 它在做什么? 答案是:類型強制。 第一個例子的這個即興演奏將起作用:

template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
float fadd(float a, float b) { return a+b; }
...
int c = do_op(4,5,fadd);

這個例子會奏效! (我並不是說它是好的 C++,但是...)發生的事情是 do_op 已經圍繞各種函數的簽名進行了模板化,並且每個單獨的實例化都會編寫不同類型的強制代碼。 因此,帶有 fadd 的 do_op 的實例化代碼如下所示:

convert a and b from int to float.
call the function ptr op with float a and float b.
convert the result back to int and return it.

相比之下,我們的按值情況需要與函數參數完全匹配。

函數指針可以作為模板參數傳遞, 這是標准 C++ 的一部分 然而,在模板中,它們被聲明並用作函數而不是函數指針。 在模板實例化時,傳遞函數的地址而不僅僅是名稱。

例如:

int i;


void add1(int& i) { i += 1; }

template<void op(int&)>
void do_op_fn_ptr_tpl(int& i) { op(i); }

i = 0;
do_op_fn_ptr_tpl<&add1>(i);

如果要將函子類型作為模板參數傳遞:

struct add2_t {
  void operator()(int& i) { i += 2; }
};

template<typename op>
void do_op_fntr_tpl(int& i) {
  op o;
  o(i);
}

i = 0;
do_op_fntr_tpl<add2_t>(i);

幾個答案將函子實例作為參數傳遞:

template<typename op>
void do_op_fntr_arg(int& i, op o) { o(i); }

i = 0;
add2_t add2;

// This has the advantage of looking identical whether 
// you pass a functor or a free function:
do_op_fntr_arg(i, add1);
do_op_fntr_arg(i, add2);

使用模板參數最接近這種統一外觀的是定義do_op兩次 - 一次使用非類型參數,一次使用類型參數。

// non-type (function pointer) template parameter
template<void op(int&)>
void do_op(int& i) { op(i); }

// type (functor class) template parameter
template<typename op>
void do_op(int& i) {
  op o; 
  o(i); 
}

i = 0;
do_op<&add1>(i); // still need address-of operator in the function pointer case.
do_op<add2_t>(i);

老實說,我真的希望這不會編譯,但它在 gcc-4.8 和 Visual Studio 2013 中對我有用

在您的模板中

template <void (*T)(int &)>
void doOperation()

參數T是一個非類型模板參數。 這意味着模板函數的行為隨着參數的值而變化(必須在編譯時固定,函數指針常量是哪些)。

如果您想要同時使用函數對象和函數參數的東西,您需要一個類型化模板。 但是,在執行此操作時,您還需要在運行時為該函數提供一個對象實例(函數對象實例或函數指針)。

template <class T>
void doOperation(T t)
{
  int temp=0;
  t(temp);
  std::cout << "Result is " << temp << std::endl;
}

有一些次要的性能考慮。 這個新版本對於函數指針參數的效率可能較低,因為特定的函數指針僅在運行時被取消引用和調用,而您的函數指針模板可以根據所使用的特定函數指針進行優化(可能是內聯的函數調用)。 函數對象通常可以使用類型化模板非常有效地擴展,盡管特定的operator()完全由函數對象的類型決定。

您的函子示例不起作用的原因是您需要一個實例來調用operator()

來到這里有額外的要求,參數/返回類型也應該有所不同。 在 Ben Supnik 之后,這將適用於某種類型的 T

typedef T(*binary_T_op)(T, T);

代替

typedef int(*binary_int_op)(int, int);

這里的解決方案是將函數類型定義和函數模板放入一個環繞的結構模板中。

template <typename T> struct BinOp
{
    typedef T(*binary_T_op )(T, T); // signature for all valid template params
    template<binary_T_op op>
    T do_op(T a, T b)
    {
       return op(a,b);
    }
};


double mulDouble(double a, double b)
{
    return a * b;
}


BinOp<double> doubleBinOp;

double res = doubleBinOp.do_op<&mulDouble>(4, 5);

或者 BinOp 可以是一個帶有靜態方法模板 do_op(...) 的類,然后稱為

double res = BinOp<double>::do_op<&mulDouble>(4, 5);

編輯:將運算符作為參考傳遞不起作用。 為簡單起見,將其理解為函數指針。 您只需發送指針,而不是引用。 我認為你正在嘗試寫這樣的東西。

struct Square
{
    double operator()(double number) { return number * number; }
};

template <class Function>
double integrate(Function f, double a, double b, unsigned int intervals)
{
    double delta = (b - a) / intervals, sum = 0.0;

    while(a < b)
    {
        sum += f(a) * delta;
        a += delta;
    }

    return sum;
}

. .

std::cout << "interval : " << i << tab << tab << "intgeration = "
 << integrate(Square(), 0.0, 1.0, 10) << std::endl;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM