Как использовать Enable_if для специализации шаблона с неопределенными аргументами

Чего я хочу достичь

Мотивация: я хочу иметь шаблон с формой.

template<typename T, typename ...Args>

где T — тип класса. И мне нужна специализированная версия этого шаблона, если T — это BaseA или любой производный класс BaseA.

(Стандарт C++, который я использую, — C++20)

Проблема

Если второй параметр шаблона определен, этого легко добиться:

template <typename T, typename U, typename Enable = void>
class Factory
{
    public:
        Factory(U arg)
        {
            m_spInner = std::make_shared<T>(arg);
        }
        void print() const
        {
            printf("Factory normal: ");
            m_spInner->print();
        }
    private:
        std::shared_ptr<T> m_spInner;
};

template <typename T, typename U>
class Factory<T, U, typename std::enable_if<std::is_base_of<BaseA, T>::value>::type>
{
public:
    Factory(U arg)
    {
        m_spInner = std::make_shared<T>(arg);
    }
    void print() const
    {
        printf("Factory BaseA: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};

// callers
int main()
{
    Factory<DerivedA1, int> factoryA1(1);
    factoryA1.print();

    Factory<DerivedA2, int> factoryA2(2);
    factoryA2.print();

    Factory<DerivedB, char> factoryB('B');
    factoryB.print();

    Factory<C, string> factoryC("C");
    factoryC.print();

    //Factory<int> factoryInt(1); // compile error
    //factoryInt.print();

    return 0;
}

делает свою работу.

Но когда второй параметр неопределенен, например

template <typename T, typename ...Args, typename Enable = void>

он не компилируется с сообщением об ошибке, что неопределенный параметр должен быть последним параметром.

Если я поставлю Enable перед ...Args, сам шаблон сможет скомпилироваться, но вызов не сработает, поскольку int не соответствует параметру шаблона Enable.

Поэтому мне интересно, можно ли заставить enable_if работать с шаблоном класса с неопределенным параметром. Или есть ли другой элегантный способ добиться необходимости специализации шаблона для класса и всего его дочернего класса?

Любое предложение приветствуется. Большое спасибо!

Приложение

Для удобства просто опубликуйте мой тестовый код ниже. Если INDEFINITE_PARAM определен, он скомпилирует версию INDEFINITE_PARAM, что приведет к ошибке, поскольку я не мог понять, как правильно поставить enable_if. если INDEFINITE_PARAM не определено, он может скомпилировать определенную версию параметра и дать ожидаемый результат.

#include <type_traits>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>

//#define INDEFINITE_PARAM

using namespace std;


class BaseA
{
public:
    BaseA(int id): m_id(id) {}
    virtual void print() const
    {
        printf("BaseA\n");
    }
private:
    int m_id;
};

class BaseB
{
public:
    BaseB(char id): m_id(id) {}
    virtual void print() const
    {
        printf("BaseB\n");
    }
private:
    char m_id;
};

class DerivedA1 : public BaseA
{
public:
    DerivedA1(int id) : BaseA(id) {}
    void print() const override
    {
        printf("DerivedA1\n");
    }
};

class DerivedA2 : public BaseA
{
public:
    DerivedA2(int id) : BaseA(id) {}
    void print() const override
    {
        printf("DerivedA2\n");
    }
};

class DerivedB : public BaseB
{
public:
    DerivedB(char id) : BaseB(id) {}
    void print() const override
    {
        printf("DerivedB\n");
    }
};

class C
{
public:
    C(string id) : m_id(id) {}
    void print() const
    {
        printf("C\n");
    }
private:
    string m_id;
};

#ifdef INDEFINITE_PARAM

template<typename T, typename Base, typename ...Args>
class Factory
{
public:
    Factory(Args&&... args)
    {
        m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
    }
    void print() const
    {
        printf("%s\n", typeid(T::Base).name());
        printf("Factory normal: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};


template <typename T, typename ...Args>
class Factory<T, BaseA, Args...>
{
public:
    Factory(Args&&... args)
    {
        m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
    }
    void print() const
    {
        printf("%s\n", typeid(T::Base).name());
        printf("Factory BaseA: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};


template <typename T, typename... Args>
class Factory<T, typename std::enable_if<std::is_base_of<BaseA, T>::value, BaseA>::type, Args...>
{
public:
    Factory(Args&&... args)
    {
        m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
    }
    void print() const
    {
        printf("%s", std::is_base_of<BaseA, T>::value);
        printf("Factory BaseA: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};
/*
template <typename T, typename... Args>
class Factory<T, void, Args...>
{
public:
    Factory(Args&&... args)
    {
        m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
    }
    void print() const
    {
        printf("Factory Normal: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};
*/

#else
template <typename T, typename U, typename Enable = void>
class Factory
{
    public:
        Factory(U arg)
        {
            m_spInner = std::make_shared<T>(arg);
        }
        void print() const
        {
            printf("Factory normal: ");
            m_spInner->print();
        }
    private:
        std::shared_ptr<T> m_spInner;
};

template <typename T, typename U>
class Factory<T, U, typename std::enable_if<std::is_base_of<BaseA, T>::value>::type>
{
public:
    Factory(U arg)
    {
        m_spInner = std::make_shared<T>(arg);
    }
    void print() const
    {
        printf("Factory BaseA: ");
        m_spInner->print();
    }
private:
    std::shared_ptr<T> m_spInner;
};
#endif

int main()
{
    Factory<DerivedA1, int> factoryA1(1);
    factoryA1.print();

    Factory<DerivedA2, int> factoryA2(2);
    factoryA2.print();

    Factory<DerivedB, char> factoryB('B');
    factoryB.print();

    Factory<C, string> factoryC("C");
    factoryC.print();

    //Factory<int> factoryInt(1); // compile error
    //factoryInt.print();

    return 0;
}

Вы неправильно используете std::enable_if. См. примечания к cppreference

3CxEZiVlQ 02.09.2024 03:25

К какой версии C++ относится ваш вопрос?

3CxEZiVlQ 02.09.2024 03:26

Стандарт С++ 20

BookSword 02.09.2024 03:27

@3CxEZiVlQ, не могли бы вы уточнить, какие примечания я нарушил, пожалуйста?

BookSword 02.09.2024 03:28

Я не знаю, как этого можно добиться, потому что Factory<DerivedA1, int> точно соответствует template <typename T, typename Base, typename... Args>, а template <typename T, typename... Args> даже не учитывается.

3CxEZiVlQ 02.09.2024 04:04

Почему вы пишете шаблон класса с переменным числом вариантов, если пакет Args... используется только в конструкторе? Сделайте ctor вариативным. P.S. вам следует сделать пример кода короче, в нем слишком много вещей, не имеющих ничего общего с вопросом, в которые никто не хочет вникать.

Gene 02.09.2024 05:15

@3CxEZiVlQ Да, это именно тот вопрос. Я понимаю, почему моя версия не работает, но не могу найти рабочую версию.

BookSword 02.09.2024 08:17

@Gene Упрощенный пример предназначен только для иллюстрации проблемы. Реальный случай должен охватывать случаи, когда BaseB, BaseC, BaseD для построения которых требуется произвольное количество аргументов, и это не определено заранее. Что касается длины примера, я думаю, что я извлек основную часть из тела вопроса, Приложение просто предоставляет код, который можно скопировать, вставить и скомпилировать.

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

Ответы 1

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

Начиная с C++20, вам больше не нужны параметры шаблона Enabler и std::enable_if/std::void_t (которые действительно нельзя использовать с вариационным шаблоном для класса). Вы можете использовать ограничения и подчинение:

template<typename T, typename ...Args>
class Factory
{
    //...
};

template<typename T, typename ...Args>
requires(std::is_base_of<BaseA, T>::value)
class Factory
{
    //...
};

Демо

Примечание. В вашем случае дополнительный Args может быть предназначен только для конструктора:

template <typename T>
requires(std::is_base_of<BaseA, T>::value)
class Factory<T>
{
public:
    template <typename... Args>
    Factory(Args&&... args)
    {
        m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
    }
// ...
};

Большое спасибо! Это действительно то, что мне нужно.

BookSword 02.09.2024 10:49

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