【问题标题】:std::function implicit type conversionstd::function 隐式类型转换
【发布时间】:2016-06-11 06:48:16
【问题描述】:

使用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&lt;-&gt;double 的一些自动转换,但为什么在第三个示例而不是第一个示例中允许它?为什么-Wconversion 没有捕捉到这个?

(不可见的精度损失是许多领域的问题,例如在处理纬度/经度时)。

【问题讨论】:

    标签: c++ templates c++11 type-conversion std


    【解决方案1】:

    正如 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 的签名(如果它发生变化)。

    【讨论】:

    • 谢谢蒂姆。在对 Elwin Arens 的回答进行一些研究后,我相信在这种情况下,Bar 的最佳定义是:template &lt;typename F = void(double)&gt; struct Bar { Bar(const F &amp;foo) : foo(foo) {} F &amp;foo; }; 。我不应该一开始就使用 std::function!
    • @lenguador 小心这个解决方案。如果 F 是一个生命周期短于被实例化的 Bar 的 lambda,那么您将陷入糟糕的境地。我同意这个解决方案可以提供更好的类型安全性,但它也会导致复杂的声明:int meow(float); auto b = Bar&lt;decltype(meow)&gt;(meow);
    • 有没有办法确保在编译时提供函数?在我的用例中,该函数在编译时是已知的,但我不希望有人错误地传递一个生命周期不正确的 lambda。
    • 我说得太早了。我测试了您的解决方案,它在使用 lambda 时编译,因为 Bar::foo 不是 const。显然,函数指针允许丢弃 CV 限定符。如果您想非常小心,请查看std::is_function
    【解决方案2】:

    这与 std::function 用于运行时多态性的目的有关,因此它使用类型擦除,这已在 here herehere 进行过讨论

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-08
      • 1970-01-01
      • 2020-08-12
      • 2020-10-28
      • 2022-01-23
      • 2013-07-09
      相关资源
      最近更新 更多