Есть ли способ, чтобы псевдонимы типов зависели от неполных типов в CRTP?

Возьмем простой пример CRTP:

template <class Target>
struct StaticCallable
{
    static auto StaticCall()
    {
        return Target().Call();
    }

    using ReturnType = decltype(Target().Call());
};

struct StaticCallableInt : StaticCallable<StaticCallableInt>
{
    int Call() { return 2; }
};

int main()
{
    StaticCallableInt::ReturnType val;
    val = StaticCallableInt::StaticCall();
}

StaticCallable<> создается с помощью StaticCallableInt, который на данный момент не является полным типом. Для StaticCall() это нормально. Вычисление откладывается до тех пор, пока не будет вызвана функция, и к тому времени StaticCallableInt станет полным типом.

Однако для псевдонима ReturnType это проблема, поскольку он оценивается на месте и к тому времени его необходимо StaticCallableInt объявить Call(). Если эта потребность не будет удовлетворена, этот код не сможет скомпилироваться.

Есть ли хорошие методы для псевдонимов типов в CRTP? Решение, которое мне пришло в голову, — использовать вспомогательный класс для всех необходимых деталей класса:

struct CallableInt
{
    int Call() { return 2; }
};

struct StaticCallableInt : StaticCallable<CallableInt> {};

Существуют ли какие-либо другие способы зависимости псевдонимов типов от неполных типов, возможно, путем отсрочки оценки псевдонима?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
113
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете добавить еще один уровень косвенности, поместив псевдоним во внутреннюю структуру.

Это отложит оценку псевдонима:

#include <iostream>

template <class Target>
struct StaticCallable
{
    static auto StaticCall()
    {
        return Target().Call();
    }
    
    // Add this struct, with the type alias inside it:
    struct RetType
    {
        using value = decltype(Target().Call());
    };
};

struct StaticCallableInt : StaticCallable<StaticCallableInt>
{
    int Call() { return 2; }
};

int main()
{
    StaticCallableInt::RetType::value val;  // use the type alias
    val = StaticCallableInt::StaticCall();
    std::cout << val;
}

Выход:

2

Живая демонстрация.

Разумно использовать класс-оболочку для задержки оценки. Несмотря на то, что класс-оболочка означает, что RetVal::type более многословен, чем RetVal<> другого решения, мне нравится, что этот подход невозможно использовать неправильно, поэтому он лучше подходит для моего варианта использования.

Joshua Maiche 24.06.2024 09:07

@JoshuaMaiche отлично, рад помочь!

wohlstad 24.06.2024 09:09

Вы можете просто создать ReturnType шаблон псевдонима, как показано ниже:

template <class Target>
struct StaticCallable
{
    static auto StaticCall()
    {
        return Target().Call();
    }

    //now this is a template 
    template<typename T = Target>
    using RetType = decltype(T().Call()); 
};

struct StaticCallableInt : StaticCallable<StaticCallableInt>
{
    int Call() { return 2; }
};

int main()
{
    StaticCallableInt::RetType<> val;      
    val = StaticCallableInt::StaticCall(); //works now
}

Рабочая демо

Действительно отличное решение! Я пытался сделать что-то подобное с помощью T = void и напрямую использовать Target, но это не сработало. Я предполагаю, что использование T делает его зависимым именем, откладывая вызов decltype до тех пор, пока StaticCallableInt не станет полным типом. Кажется, это удобный трюк!

Joshua Maiche 24.06.2024 08:56

@JoshuaMaiche Да, именно так. Непосредственное использование Target приведет к прямой замене, а не к различию замены.

user12002570 24.06.2024 09:02

Я подумал об этом решении еще немного, и одна вещь, которая меня беспокоила, — это возможность неправильного использования RetType<>, передав неправильный тип. Одним из решений этой проблемы (которое требует немного большего количества механизмов) является создание структуры, которая принудительно использует зависимое имя, но фактически не использует его: template<typename T, typename> struct PickFirst { using type = T; }; Тогда тип можно последовательно заставить быть Target, все еще используя зависимое имя. : using RetType = decltype(typename PickFirst<Target, T>::type().Call()); Пример в godbolt: godbolt.org/z/Evoc3PrTM

Joshua Maiche 25.06.2024 00:55

@JoshuaMaiche Вы также можете использовать SFINAE, например template<typename T = Target, typename = std::enable_if_t<std::is_same_v<T, Target>>> using RetType = decltype(T().Call());, чтобы другие типы не принимались.

user12002570 25.06.2024 06:23

@user12002570 RetType<Bob, void> избегает вашего SFINAE :p Нужно использовать enable_if_t<cond, bool> = true или requires или другую «более сильную» технику SFINAE

Yakk - Adam Nevraumont 29.06.2024 19:16

@Yakk-AdamNevraumont Да, requires лучше? если С++ 20 доступен.

user12002570 30.06.2024 04:23

Другие вопросы по теме