Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Extension In Visual Studio 2017 Disambiguates "bool" vs "std::function" When Passing In A Lambda?

The following compiles in Visual Studio 2017 with the MSVC compiler, but fails to compile in GCC or 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"; });
}

To fix this we can make use of enable_if like so:

#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"; });
}

Or I can disambiguate by introducing a user type instead of a bool (this is what you will need to do in the case of a constructor having the ambiguity problem):

#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"; });
}

The issue I'm having here is that I have a non-trivial sized game project and am trying to compile in Xcode for iOS. As far as I can tell I cannot get the same behaviour Visual Studio exhibits across all compilers (this would be nice). So as a result I am trying to edit my project to make it more conformant to the standard.

In an effort to do this in Visual Studio since it is my primary work environment I would like to know which non-standard extension is being used and how to disable it if at all possible. I can attempt to do this in Xcode, but for this particular problem I've found quite a few ambiguity issues and it only gives me a handful at a time.

As an additional curiosity I'm wondering if this ambiguous case has any standard proposal to address it or if Visual Studio is just going completely rogue in this case.

like image 930
M2tM Avatar asked Dec 19 '22 05:12

M2tM


2 Answers

This is caused by the interaction of two things:

  1. MSVC's captureless lambda has many conversion functions to function pointer - one per calling convention. As a result, converting a captureless lambda to bool via a function pointer is ambiguous in MSVC.

  2. Instead of treating such an ambiguous conversion as being an ambiguous conversion sequence, as the standard requires, MSVC treats it as being no conversion sequence at all, which in turn means that the bool overload is not viable. That leaves the function overload as the only viable one.

The simplest way to disable the conversion to bool is to give the lambda a capture, or a capture-default - even if it doesn't end up capturing anything, having a capture-default is sufficient to disable the conversion to function pointer.

like image 140
T.C. Avatar answered Dec 24 '22 01:12

T.C.


The lambda->bool conversion is actually lambda->function pointer->bool. As one of these doesn't qualify as a "user defined conversion", the double-conversion is considered.

In MSVC, lambda has multiple lambda->function pointer conversions, one per calling convention. This disagrees with the standard, where function pointers do not have calling convention types attached to them.

In any case, this should make the lambda->function pointer->bool conversion ambiguous (and trigger an error), but instead MSVC somehow decides to treat this ambiguity as an invalid overload instead of an error, and picks the one without ambiguity. This also appears to violate the standard.

Together, these two standard violations generate the behavior you want, mostly by accident.

I believe we can work around it in a standard compliant manner without having to write manual SFINAE at each spot. Here is an attempt:

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); }
};

now use:

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

live example.

Basically we move the fancy SFINAE into a utility class, thereby avoiding polluting the function signature with SFINAE.

like image 38
Yakk - Adam Nevraumont Avatar answered Dec 24 '22 02:12

Yakk - Adam Nevraumont