Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ TS Concepts and accessors

I wanted to use the Concepts TS to help me with data constraining. I will talk about the Concepts discussed in p0121r0 and I am using GCC 6.2 for the tests.

Take this simple piece of code:

template<typename T>
concept bool test_is_available = requires(T t) {
    t.test;
    { t.test++ };
    { t.test-- };
};

template<test_is_available T>
struct Tester
{
    T t;
};

I have to pass to the struct Tester a type with the test property, which is incrementable and decrementable. Good.

struct A
{
    unsigned test;
}

Tester<A> a;

Works as expected. Obviously, the following one will not work:

struct B
{
    std::string test;
};

struct C
{
    unsigned not_test;
};

Tester<B> b; // error: test++ and test-- are ill formed
Tester<C> c; // error: no test available

Now, the real question: why the following one is not working?

class D
{
    unsigned test;
};

Tester<D> d; // error: private???

I tried to dig into the std paper, but I am not able to understand this behaviour is expected, if the std itself is missing this possibility, if the compiler is not working correctly...

Or maybe I need to declare a sort of friendship, but what could be the point? This is a situation in which the concepts constraints needs not to be constrained by accessors...

Any ideas of what is happening here?

EDIT: It is not always easy to give the idea of the problem with a simple example. This one is a bit more complex, but more similar to the real case:

#include <cassert>
#include <utility>

template<typename T>
concept bool Countable = requires(T t) {
    t.counter;
    { ++t.counter };
    { --t.counter };
    //{ t.load(auto&&...) } -> void; <-- I am not sure how to handle this
    { t.unload() } -> void;
};

template<Countable T>
class Scoper
{
public:
    template<typename... Args>
    Scoper(T& t, Args... args) : m_t(&t)
    {
        ++t.counter;
        t.load(std::forward<Args>(args)...);
    }

    ~Scoper()
    {
        --m_t->counter;
        m_t->unload();
    }

private:
    T* m_t;
};

class Scopeable
{
public:
    unsigned getCounter() const
    {
        return counter;
    }

//private:
    //template<Countable> friend class Scoper; <-- does not work
    void load(char, int) {}
    void unload() {}
    unsigned counter = 0;
};

int main()
{
    Scopeable scopeable;
    assert(scopeable.getCounter() == 0);
    {
        Scoper<Scopeable> scoper(scopeable, 'A', 2);
        assert(scopeable.getCounter() == 1);
    }
    assert(scopeable.getCounter() == 0);
}

As you can see, it is obvious that counter, load and unload have to be private/protected, and that they only has to be accessed from Scoper. If I use an abstract base class I can only constraint counter and unload, but not load (for which, as you can see, I do not know how to handle the correct syntax...).

Maybe this will not change your answers, but the problem is probably a little bit cleaner.

like image 723
dodomorandi Avatar asked Sep 30 '16 13:09

dodomorandi


1 Answers

This concept:

template<typename T>
concept bool test_is_available = requires(T t) {
    t.test;
    { t.test++ };
    { t.test-- };
};

requires that T have a publicly accessible member test that is both post-incrementable and post-decrementable.

This type:

class D
{
    unsigned test;
};

does not have a publicly accessible member test. After all, I cannot write any of those statements:

D d;
d.test;   // error
d.test++; // error
d.test--; // error

Hence, D doesn't meet the concept test_is_available. test is very much not available.

If you want D to be a test_is_available, you'll need to make test public. You can't simply friend something, that just breaks the concept system - you'll end up with a D that's test_is_available to some types but not others. That's not really how things are supposed to work.

like image 172
Barry Avatar answered Nov 09 '22 15:11

Barry