2016-06-11 72 views
2

使用g++ (Ubuntu 4.8.5-1ubuntu1) 4.8.5g++ -std=c++11 -Wall -Wextra -Wconversion的std ::函數隱式類型轉換

下無法編譯這是預期編譯:

template <typename T> 
struct Foo { 
    Foo(T t) {} 
}; 

struct Bar { 
    Bar(Foo<float> foo) : foo(foo) {} //Trying to convert Foo<float> to Foo<double> 
    Foo<double> foo; 
}; 

下面的編譯與-Wconversion警告,預期:

void foo(float t){} 
int main() { 
    foo(3.141592653589794626); 
    return 0; 
} 

但是,以下編譯沒有警告

#include <functional> 

void foo(double t){} 

struct Bar { 
    Bar(std::function<void(float)> foo) : foo(foo) {} //Convert std::function<void(float)> to std::function<void(double)> 
    std::function<void(double)> foo; 
}; 

int main(){ 
    Bar bar(foo); //Convert std::function<void(double)> to std::function<void(float)> 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
    foo(3.141592653589794626);  //Not rounded: 3.141592653589794 
    return 0; 
} 

顯然,這是一些自動轉換float<->double但爲什麼它在第三個例子允許的,而不是第一?爲什麼-Wconversion不能捕捉到這個?

(精確度的不可見損失在許多方面都是問題,例如在使用緯度/經度時)。

回答

3

正如埃爾溫阿倫斯指出的那樣,問題是與在std::function的內部工作中進行類型擦除。有人可能會認爲,一個快速解決方法是將構造函數參數中的類型更改爲double,但這並不妨礙用戶傳入一個函數,該函數需要一個float。例如,

void foo(float t) { 
    std::cout << std::setprecision(15) << std::fixed << t << std::endl; 
} 

struct Bar { 
    Bar(std::function<void(double)> foo) : foo(foo) {} 
    std::function<void(double)> foo; 
}; 

int main() { 
    Bar bar(foo); 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
     foo(3.141592653589794626); //Not rounded: 3.141592653589794 
} 

編譯文件,但給出了令人失望的結果。一個修復它使用模板構造函數和一些TMP。

void foo(double t) { 
    std::cout << std::setprecision(15) << std::fixed << t << std::endl; 
} 

struct Bar { 
    using target_type = double; 
    using func_type = void(*)(target_type); 

    template <typename T, typename U = typename std::enable_if<std::is_same<T,func_type>::value,void>::type> 
    Bar(T foo) : foo(foo) {} 

    std::function<void(target_type)> foo; 
}; 

int main() { 
    Bar bar(foo); 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
     foo(3.141592653589794626); //Not rounded: 3.141592653589794 
} 

現在失敗,如果你在不的Bar::foo簽名匹配的函數傳遞給編譯。併發症是,您必須確保func_type匹配的簽名Bar::foo,如果它改變。

+0

謝謝蒂姆。經過Elwin Arens的回答,我相信在這種情況下Bar的最佳定義是:'template struct Bar {const F&foo}:foo(foo){} \t F &foo; }; '。我不應該一直使用std :: function來開始! – lenguador

+0

@lenguador請小心使用此解決方案。如果'F'是一個生命週期比被實例化的'Bar'更短的lambda,那麼你將會進入不好的地方。我同意這種解決方案可以提供更好的類型安全性,但它也會導致錯誤的聲明:'int meow(float); auto b = Bar (meow);' – Tim

+0

有什麼方法可以確保在編譯時提供函數嗎?在我的用例中,函數在編譯時已知,但我不希望有人錯誤地傳遞一個錯誤的生命週期的lambda。 – lenguador

2

這與標準::功能的目的做爲是用於運行時多態性,因此它使用類型擦除,已經討論hereherehere