Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Self referential c++20 concepts

What is the moral equivalent to the following invalid code?

// Suppose you want to check, as part of a concept,
// if some operation on a type results in a type that models such concept.
// you cannot constrain the resulting type with the same concept
// in the same way you can't have self referential types
// (that would result in recursive definitions)

template<class T>
concept Int = requires(const T& a, const T& b) {
  { a + b } -> Int; // compiler don't know what to do
};
like image 559
Nazinho Avatar asked Mar 03 '23 10:03

Nazinho


2 Answers

Suppose you want to check, as part of a concept, if some operation on a type results in a type that models such concept.

That's infinite recursion. Like any functional recursion, you have to have a terminal condition. The normal way to define a terminal condition for template arguments is via a specialization. But concepts explicitly cannot be specialized, so there can be no terminal condition.

It's also logically incoherent, since you're trying to write a definition by using the thing you're trying to define. There is no "moral equivalent" to something that by definition doesn't make sense.

Your concept appears to be saying "T shall be a thing that I can add to another T and yield..." what? Do you want it to be able to yield some unrelated type U which can be added to another U to yield... again, what? Even ignoring that question, should U be able to be added to T? And if so, what should that yield?

When writing a concept, start with the use case, start by deciding what operations you want to perform.

like image 51
Nicol Bolas Avatar answered Mar 11 '23 04:03

Nicol Bolas


It is possible to do such recursive template check, but it makes code difficult to read. The principle is to forward recursive template check to a function found by dependent name look up, whose constraints will only be verified if the type does not already belong to a list of already checked types... If the type belong to the list of already checked type, the function is disabled by SFINAE, and an other function that does not recursively refers to the concept is selected by overload resolution:

See it in action: compiler-explorer-link

#include <type_traits>

namespace trying{

    struct to_do{};

    template <class...Checked, class T>
    std::enable_if_t <(std::is_same_v <T,Checked> || ...), std::true_type> 
    too_complex(T &&, to_do);

    template <class...Checked, class T>
    std::false_type 
    too_complex(T &&,...);
}

template <class U, class T, class...Checked>
concept Integer_= requires(const T& a, const T& b, const U& to_be_readable)
   {
   requires decltype(too_complex <T, Checked...> (a + b, to_be_readable))::value ;
   };

template <class T, class...Checked>
concept Integer = Integer_ <trying::to_do, T, Checked...>;

namespace trying{
    template <class...Checked, class T>
    requires (Integer <T, Checked...>)
    std::enable_if_t <!(std::is_same_v <T,Checked> || ...), std::true_type> 
    too_complex(T &&, to_do);
}

struct x{
    auto
    operator + (x) const -> int;
};
struct y{
    auto
    operator + (y) const -> void*;
};

struct z2;
struct z1{
    auto
    operator + (z1) const -> z2;
};
struct z2{
    auto
    operator + (z2) const -> z1;
};

static_assert (Integer <int>);
static_assert (Integer <x>);
static_assert (!Integer <y>);
static_assert (Integer <z1>);
static_assert (Integer <z2>);

So yes it is possible... but I don't think it should be done.

like image 23
Oliv Avatar answered Mar 11 '23 03:03

Oliv