Самореференциальные концепции c ++ 20

Что является моральным эквивалентом следующего недействительного кода?

// 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
};

person Nazinho    schedule 20.05.2020    source источник
comment
Можно ли ссылаться на T вместо Int или { a + b } -> T;?   -  person Mestkon    schedule 20.05.2020
comment
@Mestkon - вы можете указать std::same_as<T> или std::convertible_to<T>. Но ни один из них не проверяет, что за OP. Представьте, если a + b приведет к типу U, который не является T и не может быть преобразован в него. OP спрашивает, соответствует ли он той же концепции.   -  person StoryTeller - Unslander Monica    schedule 20.05.2020
comment
@Mestkon Начиная с концепций C ++ 20, нет. Я думаю, что это было возможно с концепциями TS. В этом случае вы, вероятно, написали бы { ... } -> std::same<T> или { ... } -> std::convertible_to<T>   -  person Nazinho    schedule 20.05.2020
comment
@ StoryTeller-UnslanderMonica Да. Ваш комментарий был показан мне только после того, как я опубликовал   -  person Nazinho    schedule 20.05.2020
comment
Возможно ли вообще иметь возвращаемый тип в { a + b } -> Int? Я думал, что `-› Int` не был включен в окончательный стандарт.   -  person darcamo    schedule 20.05.2020
comment
@darcamo: Ты можешь получить -> ConceptName нормально. Это -> TypeName не работает.   -  person Nicol Bolas    schedule 20.05.2020
comment
@NicolBolas Спасибо за разъяснения.   -  person darcamo    schedule 20.05.2020
comment
A+A -> B и B+B->A, похоже, позволяют обоим результатам A и B передавать концепцию, или, наоборот, оба не передают концепцию ...   -  person Jarod42    schedule 20.05.2020


Ответы (2)


Предположим, вы хотите проверить, как часть концепции, не приводит ли некоторая операция к типу к типу, моделирующему такую ​​концепцию.

Это бесконечная рекурсия. Как и в любой функциональной рекурсии, у вас должно быть конечное условие. Обычный способ определить конечное условие для аргументов шаблона - через специализацию. Но concepts явно не могут быть специализированными, поэтому конечного условия быть не может.

Это также логически непоследовательно, поскольку вы пытаетесь написать определение, используя то, что вы пытаетесь определить. Не существует «морального эквивалента» тому, что по определению не имеет смысла.

Кажется, ваша концепция говорит: «T должна быть вещью, которую я могу добавить к другому T и дать ...» что? Вы хотите, чтобы он давал какой-то несвязанный тип U, который можно было бы добавить к другому U, чтобы получить ... опять же, что? Даже игнорируя этот вопрос, можно ли добавить U в T? И если да, то что это должно дать?

При написании концепции начните с варианта использования, начните с решения, какие операции вы хотите выполнить.

person Nicol Bolas    schedule 20.05.2020

Такая рекурсивная проверка шаблона возможна, но это затрудняет чтение кода. Принцип состоит в том, чтобы направить рекурсивную проверку шаблона функции, найденной зависимым поиском имени, чьи ограничения будут проверены только в том случае, если тип еще не принадлежит к списку уже проверенных типов ... Если тип принадлежит к списку уже отмеченный тип, функция отключена SFINAE, а другая функция, которая не рекурсивно ссылается на концепцию, выбирается разрешением перегрузки:

Посмотрите, как это работает: ссылка на компилятор-проводник

#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>);

Так что да, это возможно ... но я не думаю, что это нужно делать.

person Oliv    schedule 20.05.2020
comment
Таким образом, это в основном обход графа, ищущий цикл между типами, связанными вместе по результату их operator +, реализованный в TMP с использованием концепций, SFINAE и ADL одновременно. Это страшно. И круто. - person Quentin; 21.05.2020