Bug in C++20 concepts with template member functions and std::invocable

I was experimenting with C++20 concepts and the Eigen library, and I incurred an unexpected behavior. Specifically, consider the following concept requiring a type to be callable with either an Eigen::Matrix<double, -1, 1>> object or an Eigen::Matrix<char, -1, 1>> one:

template <class FOO_CONCEPT>
concept FooConcept = std::invocable<FOO_CONCEPT, Eigen::Matrix<double, -1, 1>> &&
    std::invocable<FOO_CONCEPT, Eigen::Matrix<char, -1, 1>>;

Then, look at the commented line (*) in the following struct:

struct Foo {
    // template <typename T>    <----    (*)
    void operator()(Eigen::Matrix<double, -1, 1>) {

    void operator()(Eigen::Matrix<float, -1, 1>) {

Note that the class Foo doesn't satisfy the requirements of FooConcept since it can't be called with an Eigen::Matrix<char, -1, 1> argument. Indeed:

std::cout << FooConcept<Foo> << std::endl;

prints 0. However, when I toggle the line comment (*), i.e., when the operator() is a template, the same code oddly prints 1. Is this a bug? I got these results both using Clang 12.0.1 and GCC 11.1.0 to compile the code on Visual Studio Code. Thank you for any help you can provide!

P.S.: the line

 std::cout << std::is_convertible<Eigen::Matrix<char, -1, 1>, Eigen::Matrix<float, -1, 1>>()
              << std::endl;

prints 1, but an Eigen::Matrix<char, -1, 1> object cannot be implicitly converted into an Eigen::Matrix<float, -1, 1>. Is this another bug? And is this correlated to the above problem somehow?

EDIT 1: I noticed that by defining

struct FooImplicit {
    void operator()(Eigen::Matrix<char, -1, 1>) {

the FooImplicit struct actually satisfies the FooConcept, and the same happens if you replace char with double. This looks related to the convertibility of the two Eigen types -- see P.S.

How can I express the constraint I want without allowing implicit conversions? That is, FooConcept must allow only classes that overload operator() at least twice, once with Eigen::Matrix<double, -1, 1> and once with Eigen::Matrix<char, -1, 1>. Can this be done?

Also, if I define the function

void func(FooConcept auto x) {}

and I try to call it as func(Foo()); keeping the line (*) commented, I get the following compile error:

[build] [...]: note: because 'Foo' does not satisfy 'FooConcept'
[build] void func(FooConcept auto x) {
[build]               ^
[build] [...]: note: because 'std::invocable<Foo, Eigen::Matrix<char, -1, 1> >' evaluated to false

Is this because the compiler cannot choose unambiguously which overload to call? If yes, why isn't the error message more explicit? To me, it looks just like the compiler noticed that Foo has two member functions, and one is correct, whereas the other one isn't.

EDIT 2: I managed to answer half of the question in this post. However, I'm still curious about the error message I got from the compiler.

Read more here: https://stackoverflow.com/questions/67375605/bug-in-c20-concepts-with-template-member-functions-and-stdinvocable

Content Attribution

This content was originally published by fdev at Recent Questions - Stack Overflow, and is syndicated here via their RSS feed. You can read the original post over there.

%d bloggers like this: