传入 Lambda 时,Visual Studio 2017 中的哪些扩展可以消除“bool”与“std::function”的歧义?

Posted

技术标签:

【中文标题】传入 Lambda 时,Visual Studio 2017 中的哪些扩展可以消除“bool”与“std::function”的歧义?【英文标题】:What Extension In Visual Studio 2017 Disambiguates "bool" vs "std::function" When Passing In A Lambda? 【发布时间】:2017-08-09 05:06:23 【问题描述】:

以下在 Visual Studio 2017 中使用 MSVC 编译器编译,但在 GCC 或 Clang 中编译失败。

#include <iostream>
#include <functional>

void functionTest(std::function<void()>) 
    std::cout << "F\n";


void functionTest(bool) 
    std::cout << "B\n";


int main() 
    functionTest([]() std::cout << "wut"; );

为了解决这个问题,我们可以像这样使用 enable_if:

#include <iostream>
#include <functional>

void functionTest(std::function<void()>) 
    std::cout << "F\n";


template<typename BOOL_TYPE, typename = typename std::enable_if<std::is_same<bool, BOOL_TYPE>::value>::type>
void functionTest(BOOL_TYPE) 
    std::cout << "B\n";


int main() 
    functionTest([]() std::cout << "wut"; );

或者我可以通过引入用户类型而不是 bool 来消除歧义(这是在构造函数存在歧义问题的情况下需要做的):

#include <iostream>
#include <functional>

void functionTest(std::function<void()>) 
    std::cout << "F\n";


enum class DescriptiveTypeName False, True;
void functionTest(DescriptiveTypeName) 
    std::cout << "B\n";


int main() 
    functionTest([]() std::cout << "wut"; );

我在这里遇到的问题是我有一个非平凡的游戏项目,并且正在尝试在 Xcode for iOS 中编译。据我所知,我无法在所有编译器中获得 Visual Studio 展示的相同行为(这会很好)。因此,我正在尝试编辑我的项目以使其更符合标准。

为了在 Visual Studio 中执行此操作,因为它是我的主要工作环境,我想知道正在使用哪个非标准扩展,以及如果可能的话如何禁用它。我可以尝试在 Xcode 中执行此操作,但是对于这个特殊问题,我发现了很多模棱两可的问题,而且一次只能给我一些。

作为一个额外的好奇心,我想知道这个模棱两可的案例是否有任何标准建议来解决它,或者 Visual Studio 在这种情况下是否完全是流氓。

【问题讨论】:

【参考方案1】:

这是由两件事的相互作用引起的:

    MSVC 的无捕获 lambda 具有 许多 到函数指针的转换函数 - 每个调用约定一个。因此,通过函数指针将无捕获 lambda 转换为 bool 在 MSVC 中是不明确的。

    MSVC 没有将这种模棱两可的转换视为 模棱两可的转换序列,as the standard requires 将其视为根本没有转换序列,这反过来意味着 @987654323 @重载是不可行的。这使得 function 重载成为唯一可行的重载。

禁用转换为bool 的最简单方法是给 lambda 一个捕获或捕获默认值 - 即使它最终没有捕获任何内容,具有捕获默认值也足以禁用转换到函数指针。

【讨论】:

为什么这应该是模棱两可的? lambda 的实际调用不需要添加调用运算符(这在 OP 的情况下是非法的,因为它确实返回 void):functionTest([]()-&gt;bool std::cout &lt;&lt; "wut"; return true; ()); -注意额外的 () @Swift 你误解了歧义。 lambda to function pointer to bool (aka test for null) 是转换序列; MSVC 有无数不同的函数指针中间类型。它与调用 lambda 或函数指针无关。 @Yakk 啊,因为我不认为这种转换是合法的。那不是仅在某些情况下适用的上下文转换。 MSVC at 没有将 lambdas 视为可转换为 bool 的类型,在其实现中没有定义 bool 运算符。函数得到了操作符,但 lambda 永远不会被强制转换为在此上下文中的函数 这是一个很好的答案,谢谢!我使用 Yakk 只是为了有用的 Exactly 模板。【参考方案2】:

lambda->bool 的转换其实就是 lambda->function pointer->bool。由于其中之一不符合“用户定义的转换”的条件,因此会考虑双重转换。

在 MSVC 中,lambda 有多个 lambda->函数指针转换,每个调用约定一个。这不符合标准,其中函数指针没有附加调用约定类型。

在任何情况下,这都会使 lambda->function pointer->bool 转换模棱两可(并触发错误),但 MSVC 以某种方式决定将此模棱两可视为无效重载而不是错误,并选择一个毫不含糊。这似乎也违反了标准。

这两种标准违规共同产生了您想要的行为,主要是偶然的。

我相信我们可以以符合标准的方式解决它,而无需在每个位置手动编写 SFINAE。这是一个尝试:

template<class T>
struct exactly 
  T t;
  template<class U, std::enable_if_t<std::is_same<T, std::decay_t<U>>, int> =0>
  exactly( U&& u ):t(std::forward<U>(u)) 
  exactly():t() 
  exactly(exactly&&)=default;
  exactly(exactly const&)=default;

  operator T() const&  return t; 
  operator T() &&  return std::move(t); 
  T& get()&  return t; 
  T const& get() const&  return t; 
  T get()&&  return std::move(t); 
;

现在使用:

void functionTest(exactly<bool> b) 
  std::cout << "B\n";

live example.

基本上我们将花哨的 SFINAE 移到实用程序类中,从而避免 SFINAE 污染函数签名。

【讨论】:

我将其标记为解决方案,即使其他答案更简洁,仅适用于第三种消歧方法,它允许在构造函数中使用完全 作为常规参数。跨度>

以上是关于传入 Lambda 时,Visual Studio 2017 中的哪些扩展可以消除“bool”与“std::function”的歧义?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Roslyn 在尝试重写此 lambda 时会崩溃? (Visual Studio 2015 更新 1)

Visual Studio 2015 在 constexpr 中使用 lambda

“使用 Visual Studio 调试 lambda 表达式”不再起作用?

如何配置 Visual Studio Code 以解析 AWS Lambda 层的输入路径 (javascript)

Visual Studio 即时窗口 - 不允许使用 Lambda 表达式 - 是不是有解决方法或替代方法? [关闭]

Visual Studio Code 调试项目时传参