Можно ли использовать концепции с идиомой CRTP?

Учитывая следующий пример кода с любопытным повторяющимся шаблоном (CRTP):

template<typename X>
struct Base {
  X f() const { return X{}; }
};

template<template<typename> typename T>
struct Derived : T<Derived<T>>
{};

const Derived<Base> d0{};
const Derived<Base> d1 = d0.f();

Я начал задаваться вопросом, можно ли с помощью концепций ограничить возможный набор базовых классов. Моя идея, основанная на этом ответе, предполагает использование requires B<T, Derived<T>>, где B определяется следующим образом:

#include <concepts>

// ...

template<template<typename> typename T, typename X>
concept B = requires (T<X> t)
  {
   { t.f() } -> std::convertible_to<X>;
  };

Очевидно, я не могу использовать эту форму:

template<template<typename> typename T> requires B<T, Derived<T>>
struct Derived : T<Derived<T>>
{};

потому что Derived в require-clause еще не определен. Этот:

template<template<typename> typename T>
struct Derived requires B<T, Derived<T>> : T<Derived<T>>
{};

и это:

template<template<typename> typename T>
struct Derived : T<Derived<T>> requires B<T, Derived<T>>
{};

тоже не решайте проблему.

Есть ли способ преодолеть эти трудности и объединить концепции с CRTP?

(Я проводил тесты на GCC 10.0.1.)


person user11810469    schedule 15.06.2020    source источник
comment
Учитывая следующий любопытный шаблон повторяющегося шаблона (CRTP) Это своего рода странный пример CRTP. Производный класс в CRTP обычно знает, какие базовые классы CRTP они собираются использовать. Если это делается по причинам композиции (вы намереваетесь унаследовать от Derived способом CRTP), то возможный базовый класс CRTP должен использовать тип производного класса final, а не промежуточный тип.   -  person Nicol Bolas    schedule 15.06.2020
comment
Также непонятно, какой в ​​этом будет смысл. Если Derived является промежуточным классом, который ничего не знает о том, что делает базовый класс CRTP, он должен принимать что угодно. И если Derived знает, что делает базовый класс CRTP ... тогда он либо знает, какой класс (шаблон) он использует, либо имеет некоторое представление об интерфейсе для этого шаблона. И этот интерфейс будет концепцией, определяющей базовый класс CRTP, а не производный.   -  person Nicol Bolas    schedule 15.06.2020
comment
@NicolBolas 1. Возможно, я не понял вашего комментария, но я не думаю, что мой пример странный - я даже видел вариативный CRTP, и это было нормально. 2. В моем примере я не хочу наследовать от Derived. 3. Я хочу явно определить понятие как слой абстракции между классами.   -  person user11810469    schedule 15.06.2020
comment
Я хочу явно определить концепцию как уровень абстракции между классами. Но у вас нет классов. Ваш Derived принимает шаблон, а не класс. И ваш код создает экземпляр этого шаблона с определенным типом begin. Вы не можете ограничить шаблон на основе чего-то, чего еще не существует.   -  person Nicol Bolas    schedule 15.06.2020
comment
Думаю, я не понимаю, какие отношения вы хотите здесь завязать. CRTP обычно используется для внедрения поведения в производный класс путем вставки членов, которые могут получить доступ к производному классу и / или производному классу. Вы пытаетесь наложить ограничения на конкретные дополнения?   -  person Nicol Bolas    schedule 15.06.2020
comment
@NicolBolas В моем предыдущем комментарии я пропустил слово template. Я намерен явно определить в одном месте минимальный интерфейс для базового класса.   -  person user11810469    schedule 15.06.2020


Ответы (1)


Я прочитал предложение, в котором предлагалось определить ограничение на определение класса. Но в стандарт это не включено. Итак, если вы хотите ограничить определение класса, единственный известный мне обходной путь - это использовать статическое утверждение в контексте, в котором класс полностью определен, поэтому внутри тела функции-члена:

    template <class Base>
struct A: Base 
    {

     A() {
          static_assert(a_concept <A>);
          }
    };
person Oliv    schedule 16.06.2020