问题 在std :: function 上重载


鉴于以下代码: -

#include <algorithm>
#include <iostream>
#include <functional>
#include <string>

void func(std::function<void(void)> param)
{
    param();
}

void func(std::function<void(int)> param)
{
    param(5);
}

int main(int argc, char* argv[])
{
    func([] () { std::cout << "void(void)" << std::endl; });
    func([] (int i) { std::cout << "void(int): " << i << std::endl; });

    std::string line;
    std::getline(std::cin, line);
    return 0;
}

从VS2010编译错误: -

CppTest.cpp(18): error C2668: 'func' : ambiguous call to overloaded function
1>          CppTest.cpp(11): could be 'void func(std::tr1::function<_Fty>)'
1>          with
1>          [
1>              _Fty=void (int)
1>          ]
1>          CppTest.cpp(6): or       'void func(std::tr1::function<_Fty>)'
1>          with
1>          [
1>              _Fty=void (void)
1>          ]
1>          while trying to match the argument list '(`anonymous-namespace'::<lambda0>)'
1>CppTest.cpp(19): error C2668: 'func' : ambiguous call to overloaded function
1>          CppTest.cpp(11): could be 'void func(std::tr1::function<_Fty>)'
1>          with
1>          [
1>              _Fty=void (int)
1>          ]
1>          CppTest.cpp(6): or       'void func(std::tr1::function<_Fty>)'
1>          with
1>          [
1>              _Fty=void (void)
1>          ]
1>          while trying to match the argument list '(`anonymous-namespace'::<lambda1>)'

从g ++ - 4.5编译错误

program2.cpp: In function ‘int main(int, char**)’:
program2.cpp:18:68: error: call of overloaded ‘func(main(int, char**)::<lambda()>)’ is ambiguous
program2.cpp:6:10: note: candidates are: void func(std::function<void()>)
program2.cpp:11:10: note:                 void func(std::function<void(int)>)
program2.cpp:19:79: error: call of overloaded ‘func(main(int, char**)::<lambda(int)>)’ is ambiguous
program2.cpp:6:10: note: candidates are: void func(std::function<void()>)
program2.cpp:11:10: note:                 void func(std::function<void(int)>)

所以似乎编译器无法弄清楚lambda []() - > void只能分配给std :: function <void(void)>,而lambda [](int) - > void只能被赋值给std :: function <void(int)>。这是应该发生还是仅仅是编译器的缺陷?


8552
2018-01-19 05:08


起源



答案:


这是应该发生还是仅仅是编译器的缺陷?

这应该发生。 std::function 有一个构造函数模板,可以采取任何类型的参数。在选择构造函数模板并实例化之后,编译器才能知道它将会遇到错误,并且必须能够在它执行此操作之前选择函数的重载。

最直接的解决方法是使用强制转换或显式构造 std::function 对象类型正确:

func(std::function<void()>([](){}));
func(std::function<void(int)>([](int){}));

如果你有一个支持captureless-lambda-to-function-pointer转换的编译器而你的lambda没有捕获任何东西,你可以使用原始函数指针:

void func(void (*param)()) { }
void func(void (*param)(int)) { }

(看起来你使用的是Visual C ++ 2010,它不支持这种转换。直到Visual Studio 2010发布之前,转换才被添加到规范中,为时已太晚而无法添加。)


要更详细地解释问题,请考虑以下事项:

template <typename T>
struct function {

    template <typename U>
    function(U f) { }
};

这基本上是什么的 std::function 有问题的构造函数看起来像:你可以用任何参数调用它,即使参数没有意义并且会在其他地方引起错误。例如, function<int()> f(42); 将调用此构造函数模板 U = int

在您的特定示例中,编译器在重载解析期间找到两个候选函数:

void func(std::function<void(void)>)
void func(std::function<void(int)>)

参数类型,我们将引用的一些不可言说的lambda类型名称 F,这些都不完全匹配,所以编译器开始查看它可以做什么转换 F 尝试使其匹配这些候选函数之一。在查找转换时,它会找到上述构造函数模板。

此时所有编译器都看到它可以调用任一函数,因为

  • 它可以转换 F 至 std::function<void(void)> 使用其转换构造函数 U = F 和
  • 它可以转换 F 至 std::function<void(int)> 使用其转换构造函数 U = F

在你的例子中,很明显只有其中一个会成功而没有错误,但在一般情况下并非如此。编译器无法继续做任何事情。它必须报告歧义并失败。它不能选择一个,因为两个转换同样好,并且过载都不比另一个好。


13
2018-01-19 05:40



告诉我,如果我做对了。出于同样的原因,这个片段有同样的问题(不能及时实例化Struct以确定func重载)? template <typename T> struct Struct { Struct(T var) {} }; void func2(Struct<int> obj){} void func2(Struct<double> obj){} int main() { func2(2.5); } - Arnavion
不,这有点不同。那个错误是因为 T 处于非受限的背景中。我会在我的答案中编辑一个解释;这个评论不够大。 - James McNellis
得到它了。谢谢! - Arnavion
在C ++标准草案的许多方面,如果不满足某些条件,委员会要求从重载决策中排除一些构造函数/函数模板。这通常可以通过“SFINAE”来实现。 (例如,参见shared_ptr的模板化构造函数)。我想知道为什么他们没有用std :: function做类似的事情。 - sellibitze
@sellibitze:我认为这可能会很困难,因为有很多不同类型的类型可以存储 std::function。即便如此,在OP的情况下,仍然可能存在歧义,例如:如果你传递了一个类型的对象 struct S { void operator()() { } void operator()(int) { } };。 - James McNellis


答案:


这是应该发生还是仅仅是编译器的缺陷?

这应该发生。 std::function 有一个构造函数模板,可以采取任何类型的参数。在选择构造函数模板并实例化之后,编译器才能知道它将会遇到错误,并且必须能够在它执行此操作之前选择函数的重载。

最直接的解决方法是使用强制转换或显式构造 std::function 对象类型正确:

func(std::function<void()>([](){}));
func(std::function<void(int)>([](int){}));

如果你有一个支持captureless-lambda-to-function-pointer转换的编译器而你的lambda没有捕获任何东西,你可以使用原始函数指针:

void func(void (*param)()) { }
void func(void (*param)(int)) { }

(看起来你使用的是Visual C ++ 2010,它不支持这种转换。直到Visual Studio 2010发布之前,转换才被添加到规范中,为时已太晚而无法添加。)


要更详细地解释问题,请考虑以下事项:

template <typename T>
struct function {

    template <typename U>
    function(U f) { }
};

这基本上是什么的 std::function 有问题的构造函数看起来像:你可以用任何参数调用它,即使参数没有意义并且会在其他地方引起错误。例如, function<int()> f(42); 将调用此构造函数模板 U = int

在您的特定示例中,编译器在重载解析期间找到两个候选函数:

void func(std::function<void(void)>)
void func(std::function<void(int)>)

参数类型,我们将引用的一些不可言说的lambda类型名称 F,这些都不完全匹配,所以编译器开始查看它可以做什么转换 F 尝试使其匹配这些候选函数之一。在查找转换时,它会找到上述构造函数模板。

此时所有编译器都看到它可以调用任一函数,因为

  • 它可以转换 F 至 std::function<void(void)> 使用其转换构造函数 U = F 和
  • 它可以转换 F 至 std::function<void(int)> 使用其转换构造函数 U = F

在你的例子中,很明显只有其中一个会成功而没有错误,但在一般情况下并非如此。编译器无法继续做任何事情。它必须报告歧义并失败。它不能选择一个,因为两个转换同样好,并且过载都不比另一个好。


13
2018-01-19 05:40



告诉我,如果我做对了。出于同样的原因,这个片段有同样的问题(不能及时实例化Struct以确定func重载)? template <typename T> struct Struct { Struct(T var) {} }; void func2(Struct<int> obj){} void func2(Struct<double> obj){} int main() { func2(2.5); } - Arnavion
不,这有点不同。那个错误是因为 T 处于非受限的背景中。我会在我的答案中编辑一个解释;这个评论不够大。 - James McNellis
得到它了。谢谢! - Arnavion
在C ++标准草案的许多方面,如果不满足某些条件,委员会要求从重载决策中排除一些构造函数/函数模板。这通常可以通过“SFINAE”来实现。 (例如,参见shared_ptr的模板化构造函数)。我想知道为什么他们没有用std :: function做类似的事情。 - sellibitze
@sellibitze:我认为这可能会很困难,因为有很多不同类型的类型可以存储 std::function。即便如此,在OP的情况下,仍然可能存在歧义,例如:如果你传递了一个类型的对象 struct S { void operator()() { } void operator()(int) { } };。 - James McNellis