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
};
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 concept
s 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With