std::function 隐式类型转换
std::function implicit type conversion
使用 g++ (Ubuntu 4.8.5-1ubuntu1) 4.8.5
并使用 g++ -std=c++11 -Wall -Wextra -Wconversion
编译
以下未按预期编译:
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
没有捕捉到这个?
(无形的精度损失是许多领域的一个问题,例如在使用 latitude/longitude 时)。
这与 std::function 的目的有关,用于运行时多态性,因此它使用类型擦除,这已被讨论 here here and
正如 Elwin Arens 指出的那样,问题出在 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
的签名,如果它改变的话。
使用 g++ (Ubuntu 4.8.5-1ubuntu1) 4.8.5
并使用 g++ -std=c++11 -Wall -Wextra -Wconversion
以下未按预期编译:
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
没有捕捉到这个?
(无形的精度损失是许多领域的一个问题,例如在使用 latitude/longitude 时)。
这与 std::function 的目的有关,用于运行时多态性,因此它使用类型擦除,这已被讨论 here here and
正如 Elwin Arens 指出的那样,问题出在 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
的签名,如果它改变的话。