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.