2016-06-11 101 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