什么扩展在Visual Studio 2017消歧“bool”vs“std :: function”何时传入Lambda?

问题描述:

以下内容在Visual Studio 2017中用MSVC编译器编译,但无法在GCC或Clang中编译。什么扩展在Visual Studio 2017消歧“bool”vs“std :: function”何时传入Lambda?

#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编译为iOS。据我所知,我无法获得Visual Studio在所有编译器中展示的相同行为(这很好)。因此,我试图编辑我的项目,使其更符合标准。

为了在Visual Studio中做到这一点,因为它是我的主要工作环境,我想知道哪些非标准扩展正在使用以及如何尽可能禁用它。我可以尝试在Xcode中这样做,但对于这个特定的问题,我发现了很多含糊不清的问题,它一次只给我一小撮。

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

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的函数签名。

+1

我将此标记为解决方案,即使其他答案只是第三种消歧方法更为简洁,它允许在构造函数中将用作常规参数。 – M2tM

这是由两件事共同作用的结果:

  1. MSVC的captureless拉姆达有许多转换功能为一个函数指针 - 每调用约定之一。因此,通过函数指针将无捕获lambda转换为bool在MSVC中不明确。

  2. 相反治疗这样一个不明确的转换作为一个暧昧转换序列as the standard requires的,MSVC将其视为是没有转换序列在所有的,而这又意味着,bool过载是不可行的。这使得超载成为唯一可行的过载。

禁用转换为bool最简单的方法就是给拉姆达捕获,或捕获默认 - 即使它并没有结束拍摄什么,有捕获默认足以禁用转换为函数指针。

+0

为什么这应该是含糊不清的? lambda的实际调用需要添加一个调用操作符(这在OP的情况下是非法的,因为它确实返回void):'functionTest([]() - > bool {std :: cout Swift

+2

@Swift你误解了模糊性。 lambda函数指向bool(aka test for null)是转换序列; MSVC有许多不同的函数指针中间类型。它与调用lambda或函数指针无关。 – Yakk

+0

@Yakk啊,因为我并不认为这种转换是合法的。这不会是仅适用于某些情况下的上下文转换。 MSVC at不会将lambdas看作可转换为bool的类型,在其实现中没有定义bool运算符。函数得到了操作符,但lambda从不会被铸造在这方面的功能 – Swift