1

I am trying to construct a virtual equal operator, could you please help? equalable is a class such that all inherting classes require the is_equal functions between type(*this).

When I delete the requiere it compiles, but I didn't test to see if the code is correct, just trying to make it compile (Error is at the end of the code):

#include <iostream>
#include <concepts>
#include <memory>

template<class T>
concept has_is_equal = requires(T x, T y)
{
    { x.is_equal(y) } ->std::same_as<bool>;
};

template <class T>
requires has_is_equal<T>
class equalable
{
public:
    using derived_type = T;

    template<typename U>
    bool operator==(const U& other) const
    {
        return this->equal<U>(other);
    }
private:

    template<class U>
    bool equal(const equalable<U>& other) const
    {
        return false;
    };

    template<>
    bool equal<T>(const equalable<T>& other) const
    {
        const T& c_other = static_cast<const T&>(other);
        const T& c_this  = static_cast<const T&>(*this);
        return c_this.is_equal(c_other);
    };
};


class derived_one : public equalable<derived_one>
{
public:

    int a = 1;

    bool is_equal(const derived_one& other) const
    {
        return a == other.a;
    }
};

class derived_two : public equalable<derived_two>
{
public:

    int a = 2;

    bool is_equal(const derived_two& other) const
    {
        return a == other.a;
    }
};


int main()
{
    derived_one one; one.a = 1;
    derived_one two; two.a = 2;

    bool alpha = (one == two);
    std::cout << alpha;
}

Error message is error, I am using VS with this config Could you please help?

Thanks very much!

Vero
  • 313
  • 2
  • 9
  • 1
    I reopened the question because I don't think it's really a duplicate. Yes, it's true that the particular CRTP idiom that the OP is trying to use will not work, but as I see it, the OP's real question is whether it's possible to create a base class that will make it so that a compile-time error will occur if a derived class doesn't satisfy the requirement. Perhaps there are ways of accomplishing this other than the way the OP tried that doesn't work. – Brian Bi Apr 17 '22 at 18:00
  • Could please someone help, this shouldn't be complicated for someone familiar with concepts :) Please just a hint so I can move on! – Vero Apr 17 '22 at 18:22
  • Why not design a pure virtual interface class. Define the method you want. Then write a concept, if you still feel you need one, that checks to see if it’s that class? – Taekahn Apr 17 '22 at 18:33
  • If I do that I lose the template arg for == operator, meaning I have to take the pure virtual base class as input i.e. bool operator==(const equalable& other) const = 0 and all other childs will have to overload this, however each one of them has to cast other to its type, check if the cast succeeded and do the comparison, I find this very ugly. If this is not wht you had in mind, could please give more details? – Vero Apr 17 '22 at 18:36
  • @BrianBi: "*the OP's real question is whether it's possible to create a base class that will make it so that a compile-time error will occur if a derived class doesn't satisfy the requirement.*" It's **the same problem**: the derived class is incomplete. It has no members to test, whether they're member typedefs or member functions. I would literally just be copy-and-pasting the answer, maybe replacing the word "alias" with "function". – Nicol Bolas Apr 17 '22 at 18:46
  • 1
    @Vero: "*Could please someone help, this shouldn't be complicated for someone familiar with concepts*" It's impossible, [for these reasons](https://stackoverflow.com/a/54313796/734069). – Nicol Bolas Apr 17 '22 at 18:49
  • @NicolBolas I don't think that's the end of the story, however. Obviously, any approach that attempts to check the condition before the derived class is complete cannot work. Might there be approaches that check the condition after the derived class is complete? – Brian Bi Apr 17 '22 at 18:53
  • @BrianBi: Sure, you could remove the check from the base class and put it in a `static_assert` or something after each derived class. But that breaks the whole point of wanting the base class to have the check: ensuring that a user doesn't make a mistake. Like forgetting to `static_assert` on a check. – Nicol Bolas Apr 17 '22 at 18:55

1 Answers1

2

Concepts do not suspend the rules of C++. The concept is checked when you attempt to instantiate the assocaited template. This happens when you write equalable<derived_one> as the base class of derived_one. This is the moment when the compiler determines if derived_one matches the requested concept.

At that moment, all that C++ knows about derived_one is that it is a class. None of its body has been processed because that body is after the base class declaration. The language cannot look ahead at the declarations in the class definition yet because it doesn't even know what the base classes are. Those declarations can reference properties of the base classes, after all.

As such, any CRTP-like mechanism can only know the most basic properties of the derived class. Namely, that the type in fact names a class. That's it. It is an incomplete type, so there's not much more you can do with that typename.

The reason your equal<T> function works is because the compiler defers compiling the body of those member functions until later. When the compiler gets around to compiling the function body, the derived class is complete.

What you really want is to insert some functionality into an existing class, based on that class's existing interface. You're just using a base class as a method to do that. But that method (the CRTP) comes with limitations; this is one of them.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982