Чего я хочу достичь
Мотивация: я хочу иметь шаблон с формой.
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;
}
К какой версии C++ относится ваш вопрос?
Стандарт С++ 20
@3CxEZiVlQ, не могли бы вы уточнить, какие примечания я нарушил, пожалуйста?
Я не знаю, как этого можно добиться, потому что Factory<DerivedA1, int> точно соответствует template <typename T, typename Base, typename... Args>, а template <typename T, typename... Args> даже не учитывается.
Почему вы пишете шаблон класса с переменным числом вариантов, если пакет Args... используется только в конструкторе? Сделайте ctor вариативным. P.S. вам следует сделать пример кода короче, в нем слишком много вещей, не имеющих ничего общего с вопросом, в которые никто не хочет вникать.
@3CxEZiVlQ Да, это именно тот вопрос. Я понимаю, почему моя версия не работает, но не могу найти рабочую версию.
@Gene Упрощенный пример предназначен только для иллюстрации проблемы. Реальный случай должен охватывать случаи, когда BaseB, BaseC, BaseD для построения которых требуется произвольное количество аргументов, и это не определено заранее. Что касается длины примера, я думаю, что я извлек основную часть из тела вопроса, Приложение просто предоставляет код, который можно скопировать, вставить и скомпилировать.





Начиная с 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)...);
}
// ...
};
Большое спасибо! Это действительно то, что мне нужно.
Вы неправильно используете
std::enable_if. См. примечания к cppreference