How to use a C++ requires clause in a concept to require a member variable to satisfy a concept constraint?

Viewed 2592

I was watching a C++ 20 Concepts Presentation , and when attempting to reproduce the code I seem to be stuck.

I am trying to require that the root of the tree should satisfy the MyObjConcept0_, which for simplicity's sake is just an int. How come when I use this concept in the requires clause of the Tree_ concept the results are false?

I tried to copy the code directly from the presentation and still no luck. Why is the return type of the { t.root } clause an int& - I mean it makes sense, because when you access a member that way you get a reference.

So how come in the presentation at 39:00 this(same as MyObjConcept0_) requires clause passes?

Did something change in the standard from the point of this presentation or am I blindly missing something?

#include <concepts>
#include <functional>

// Type is an int
template<typename T>
concept MyObjConcept0_ = std::same_as<T,int>;

// Type is any type that decays to int
template<typename T>
concept MyObjConcept1_ = std::same_as<std::decay_t<T>,int>;

// Type is an int&
template<typename T>
concept MyObjConcept2_ = std::same_as<T,int&>;



template<typename T>
concept Tree_ = requires (T t) {
    { t.root } -> MyObjConcept0_;          // does not work : This is the concept I want to use  
    { t.root } -> MyObjConcept1_;          // works but will pass for int and int& : unsafe
    { t.root } -> MyObjConcept2_;          // works but checks that t.root is an int&
    std::same_as<decltype(t.root),int>; // works: verbose and not a concept
};

template<MyObjConcept0_ MyObjConcept0T>
struct tree {
    MyObjConcept0T root;
};

static_assert(Tree_<tree<int>>);
3 Answers

The compound-requirement

{ e } -> Concept;

means that e must be a valid expression and Concept<decltype((e))> must hold. Note the double parentheses, this is important. Let's just take a simpler tree, I don't know why this needs to be a template:

struct X {
    int root;
};

X t;

While decltype(t.root) is int (the declared type of that member variable is int), decltype((r.root)) is int& (because it is an lvalue of type int, hence int&). As a result:

template <typename T>
concept Tree = requires(T t) {
    { t.root } -> std::same_as<int&>;
};

Tree<X> holds - because t.root is an lvalue of type int.


clang simply gets this wrong. It does not implement P1084, this is #45088.

Requirements check types of expressions. In order to assess the type of a variable, you have to check an expression that encodes decltype of your variable.

template <typename T>
struct wrap{};

// member declared as `int`
{ wrap<decltype(t.intv)>() } -> std::same_as<wrap<int>>;

// member declared as `int&`
{ wrap<decltype(t.intlrefv)>() } -> std::same_as<wrap<int&>>;

// member declared as `int&&`
{ wrap<decltype(t.intrrefv)>() } -> std::same_as<wrap<int&&>>;

Thank you all or your answers, they pointed me in the right direction but they were not generic enough form my problem. As the key problem was passing a concept type to the requires clause of another concept. I should-of specified that.

After couple hours on compiler explorer I figured out a solution. Working in clang 10.0.0 and up with -std=c++20 :

#include <type_traits>
#include <functional>


template<typename T>
concept Node_ = std::is_object_v<T>; 

template<typename T>
concept Tree_ = requires (T t) {
    { t.root } -> Node_;
    /* implement child tuple constraints here */
};

// Generic tree object 
template<Node_ NodeT /*, Tree_ ... ChildrenT*/>
struct tree {
    NodeT root;
    /* implement child tuple here */
};

struct test_tree_int { int root; }; // Should satisfy tree concept
struct test_tree_intref { int& root; }; // Shouldnt satisfy tree concept


static_assert(Tree_<tree<int>>);
static_assert(Tree_<test_tree_int>);
//static_assert(Tree_<test_tree_intref>); /// Compilation fails

And the reason why this didn't work for me earlier is because I was using GCC 10.2 which does in-fact have the compiler issue mentioned by Barry: the double parentheses in Concept<decltype((e))> produces a reference.

Seems like all the solutions will increase code bloat which will make using the concepts more of a hassle than help.

Related