I've a C++ concept where I need to check that the class has a particular public attribute.
My problem is that the concept works if I use it directly, but fails if I use it in std::visit.
This is the working example:
#include <type_traits>
#include <concepts>
template <typename T>
concept ControllerConcept = [] {
return requires (T controller) {
{ controller.tick(0.0) } -> std::convertible_to<double>;
{ T::Name } -> std::convertible_to<const char*>;
};
}();
class TestClass {
public:
inline constexpr static char Name[]{"TheName"};
public:
double tick(double t) {
return t * 0.5;
}
};
int main(int argc, char* argv[]) {
TestClass value{};
static_assert(ControllerConcept<decltype(value)>, "Controller does not satisfy ControllerConcept.");
return 0;
}
This works in MSVC, Clang and GCC: if I comment the Name row in TestClass compilers give an error.
Now things become a bit more complicated. I want to create in my code many classes, and put them into a std::variant. I also want to check that the classes satisfies the ControllerConcept, so I put the static assert inside the visitor (where I use the VariantOverload tecnique. This is my new code:
#include <type_traits>
#include <concepts>
#include <variant>
// Definition of the variant overload utility
template<typename ... Ts>
struct VariantOverload : Ts ... {
using Ts::operator() ...;
};
template<class... Ts> VariantOverload(Ts...) -> VariantOverload<Ts...>;
// This is the usual concept
template <typename T>
concept ControllerConcept = [] {
return requires (T controller) {
{ controller.tick(0.0) } -> std::convertible_to<double>;
{ T::Name } -> std::convertible_to<const char*>;
};
}();
// The class inside the variant
class TestClass {
public:
inline constexpr static char Name[]{"TheName"};
public:
double tick(double t) {
return t * 0.5;
}
};
// The variant
using Value = std::variant<
TestClass
>;
int main(int argc, char* argv[]) {
Value value = TestClass{};
// I'm visiting the visitor in order to call the method, and inside it I check if the class
// satisfies the concept
VariantOverload visitor{
[](auto& controller) -> double {
static_assert(ControllerConcept<decltype(controller)>, "Controller does not satisfy ControllerConcept.");
return controller.tick(2.0);
}
};
return std::visit(visitor, value);
}
Now the compilation fails in MSVC, Clang and GCC, and all errors say that the concept is not satisfied (as far as I understand).
I was thinking an error on my code, but the strange thing is that if I comment the { T::Name } -> std::convertible_to<const char*>; in the concept, I can compile the code successfully even in the second case, so I think that the code for checking the concept is correct.
So I don't understand what's happening. If the concept works with the first code snippet, why it does not work anymore in the second one?
And if I check the concept correctly inside the visitor, why T::Name check works in the first case but not in the second?
I think that's my error, not a compiler one, first because I'm not smart enough to give errors to compilers, and also because I've the problem with all three compilers.
Can you help me? What's the correct way to use the concept in the second case?
Your example can be reduced to the following
#include <concepts>
#include <variant>
template <typename T>
concept SomeConcept = requires (T x) {
{ T::Name } -> std::convertible_to<const char*>;
};
struct TestClass {
inline constexpr static char Name[]{"TheName"};
};
int main(int argc, char* argv[]) {
std::variant<TestClass> value;
std::visit([](auto& x) {
static_assert(SomeConcept<decltype(x)>, "SomeConcept failure.");
}, value);
}
gcc for instance will complain with
<source>:18:23: note: constraints not satisfied
<source>:5:9: required by the constraints of 'template<class T> concept SomeConcept'
<source>:5:23: in requirements with 'T x' [with T = TestClass&]
where you can observe that the deduced type for T is TestClass&, a reference on TestClass that can't match with your concept.
You can either:
auto parameter in the lambda functionstd::remove_reference so your concept will check TestClass and not TestClass&