使用 std::function 时选择自动 return 类型的调用运算符而不是构造函数

Call operator with auto return type being chosen instead of constructor when using std::function

以下片段:

#include <functional>

struct X {
    X(std::function<double(double)> fn); // (1)
    X(double, double);                   // (2)

    template <class T>
    auto operator()(T const& t) const {  // (3)
        return t.foo();
    }
};

int main() {
    double a, b;
    auto x = X(a, b);
    return 0;
}

...使用 -std=c++14 时无法同时使用 clang (4.0.1) 和 g++ (6.3, 7.2) 进行编译 — 在 OSX 上测试并且godbolt.org.

但是编译没有问题如果:

也许我在这里遗漏了一些明显的东西...这段代码有什么问题吗?这是一个错误吗?

您正在复制初始化 xX 是一个棘手的类型,原因如下:

  1. 它可以从任何可以用双参数调用的可调用对象构造。并且其 return 类型可转换为 double。

  2. 它本身就是一个可调用对象,可以用双参数调用。但是return类型需要推导。

  3. 编译器为 X 生成了复制构造函数。

我希望歧义在这里开始变得明显。需要重载解析。

当您删除第一个 c'tor 时,它以明显的方式消除了歧义。有趣的案例是函数调用运算符。

你看,std::function 只能从传递给它的可调用对象构造(模板化的 c'tor 将只参与重载决议)如果参数到参数之间的转换,以及 return 类型是可能的。这一切都是在未评估的上下文中完成的,因此模板化函数调用运算符未被 ODR 使用,因此未被实例化。

当return 类型是占位符类型时,std::function c'tor 无法轻易判断是否应该构造。在重载解析期间编译失败,即使它成功了,也会选择 X 的复制 c'tor。


正如@VTT 在评论中建议的那样,将接受 std::function 的 c'tor 标记为显式也将解决歧义。这一切都是因为编译器根本不必对隐式转换序列进行排序。