Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::visit in an unsatisfied concept cause a compile error in gcc

This code:

#include <concepts>
#include <string>
#include <variant>

struct any_callable {
public:
    template<typename T>
    void operator()(T&&) {}
};

template<typename V>
concept is_variant = requires(V v) { std::visit(any_callable{}, v); };

int main() {
    constexpr bool wrapped = is_variant<std::string>;
}

does not compile under gcc 11. It gives a bunch of errors about valueless_by_exception and such. It does, however, compile under msvc (godbolt). Now as I understand concepts, in this instance, if it would usually fail to compile it will decay to false, otherwise to true. The behaviour of msvc would seem to support this.

So: Is this a bug in gcc, a non-standard feature of msvc, and/or is my code wrong?

like image 944
Object object Avatar asked Jun 24 '21 12:06

Object object


1 Answers

That this:

template<typename V>
concept is_variant = requires(V v) { std::visit(any_callable{}, v); };

works at all is a very recent change, a result of P2162. In order for this check to work, you need std::visit to be what's usually referred to as "SFINAE-friendly". That is: it must somehow be constrained on "variant-ness" such that if V is a variant, it works, and if V is not a variant, then visit is removed from the overload set such that this call is an invalid expression (so that the concept can be rejected).

However, prior to P2162, there were no Constraints on std::visit. It was not SFINAE-friendly: the call would either work or be ill-formed. It was this paper that added the constraint that the types that you're passing into it are variants (or inherit from one). That's why you're seeing the error you're seeing: the call to visit was failing, but not in a way that was friendly to the concept check.

Post-P2162 (which, as the paper points out, MSVC already implemented, but libstdc++ did not), your check will be valid.

But we can do this easier in C++20 without having to go into the variant machinery - by checking the variant-ness directly in the same way that the paper does it:

template<typename V>
concept is_variant = requires(V v) {
    []<typename... Ts>(std::variant<Ts...> const&){}(v);
};

That lambda is invocable with v if either v is a variant or it inherits from one. Otherwise, it's ill-formed. This is more straight-forward than going through visit.

like image 91
Barry Avatar answered Oct 07 '22 18:10

Barry