Я хочу, чтобы мой абстрактный базовый класс имел шаблонный конструктор, но не могу заставить его работать без дополнительного параметра, из которого можно вычесть тип.
Я хочу сделать что-то вроде этого:
class Base {
public:
template <typename T>
Base() {
/*Do something*/
}
virtual void foo() = 0;
};
class Derived : public Base {
public:
Derived() : Base</*Some type*/>() {} // <-- Forbidden
void foo () {}
};
Я знаю, что это не работает для неабстрактных классов, потому что синтаксис для их создания будет приводить к тому, что шаблон конструктора и возможная спецификация шаблона класса будут конфликтовать* друг с другом, но я не понимаю, почему это не будет работать для абстрактных классов, поскольку вы не можете создать их экземпляры самостоятельно.
*(Например, с Base<int>()
(при условии, что база не является абстрактной), неясно, для чего предназначен указанный шаблон. Шаблон класса или шаблон функции конструктора.)
Любая помощь будет принята с благодарностью! :)
template <typename T> Base()
— бесполезный конструктор. Вы не можете вызвать конструктор, поэтому невозможно указать, что такое T
.
Базовый класс производного класса должен быть классом шаблона, но вы определили шаблонный конструктор (где шаблон должен использоваться только для аргументов конструктора).
@S.Gleissner, с другой стороны, шаблонирование самого базового класса затруднит, если не сделает невозможным, создание нескольких производных классов с общим базовым классом, в зависимости от того, как они используются.
@NathanOliver Ну, вот в чем дело, я хочу вызвать его из производного класса и определить T
там, чтобы использовать его в упомянутом вами конструкторе базового класса.
@S.Gleissner Ответ Реми Лебо на ваш комментарий также является причиной того, что я не использую шаблон класса. Кроме того, поскольку мне это нужно только в конструкторе.
Если вы заставите конструктор принимать параметр T
, производный класс сможет передать его в базу. Derived() : Base(T{}) {}
@UniCode Это твоя проблема. Вы никогда не вызываете конструктор. Это не то, что вы можете сделать. Вам нужно либо передать конструктору параметр типа T
, чтобы его можно было вывести при использовании, либо создать Base
шаблон класса.
@BoP: std::type_identity<T>
даже лучше.
«Я хочу, чтобы у моего абстрактного базового класса был шаблонный конструктор» — думаю, именно здесь произошла ошибка. Какова конечная цель этого? Почему это важно?
@TedLyngmo Ну, я пишу систему поведения для элементов пользовательского интерфейса в моем (своего рода) движке, и я хочу, чтобы каждая база элементов была инициализирована с одинаковым поведением по умолчанию. Для этой цели у меня есть метод initBehaviors()
, который добавляет все поведения к объекту BehaviorManager
, который есть у каждого элемента пользовательского интерфейса. Для добавления мне нужно знать владельца, а также параметры каждого поведения. И вот для чего мне это нужно. Я хочу, чтобы владелец всегда имел производный тип, но просто не знал об этом в базовом классе + я хочу всегда инициализировать поведение по умолчанию, поэтому оно должно идти в конструкторе
@TedLyngmo Надеюсь, вы хоть немного поняли, но важно отметить, что это решение будет не единственным, которое я вижу, но точно единственным чистым :). По сути, я просто хочу иметь возможность делать Button(...) : UiElement<Button> {}
(например) в каждом элементе пользовательского интерфейса, что все еще немного избыточно, но самое чистое, что я могу придумать на данный момент.
в чем именно вопрос? Что плохого в использовании параметра для определения типа?
@Jarod42 К сожалению, std::type_identity
— это черта трансформации, которая не обязательно должна быть конструктивной. Вместо этого используйте std::in_place_type
.
@UniCode Я рад, что ты получил ответ. Для меня это стало похоже на вопрос XY.
@TedLyngmo Да, я понимаю, что вы имеете в виду, но даже помимо моего варианта использования мне все равно хотелось бы знать, возможно ли это и как это возможно или нет. Просто для лучшего понимания C++ в целом.
Я полагаю, вы не хотите передавать параметр типа T
, который позволит вам сделать вывод T
, потому что параметр будет неиспользован. В противном случае вы могли бы просто сделать это.
Вы можете использовать тег, единственная цель которого — передать информацию, для которой следует использовать T
, и передать ее в качестве параметра:
template <typename T> struct tag{};
template <typename tag> struct T_from_tag;
template <typename T> struct T_from_tag<tag<T>> { using type = T;};
template <typename tag> using T_from_tag_t = typename T_from_tag<tag>::type;
class Base {
public:
template <typename tag>
Base(tag) {
using T = T_from_tag_t<tag>;
}
virtual void foo() = 0;
};
class Derived : public Base {
public:
Derived() : Base(tag<Derived>{}) {}
void foo () {}
};
То, что вы предлагаете, не будет работать, например, когда Base
сам по себе является шаблоном. Base<T><U>
просто это не синтаксис C++. Я полагаю, что было бы возможно изобрести язык, который сделал бы то, что вы хотите, возможным более непосредственно, но в C++ это не требуется, поскольку есть другие способы, например, использование тега, как показано выше.
PS: Как отметил Раймонд Чен, в C++17 введен стандартный шаблон для достижения того же: std::in_place_type<T>:
#include <iostream>
#include <utility>
#include <type_traits>
struct foo {
template <typename T>
foo(std::in_place_type_t<T>) {
if constexpr (std::is_same_v<int,T>) {
std::cout << "T is int\n";
}
}
};
int main() {
foo{std::in_place_type<int>}; // prints
foo{std::in_place_type<double>}; // does not print
}
Для этой цели в C++ имеется встроенный тип тега. in_place_type<T>. Итак, вы пишете template<typename T> Base(std::in_place_type_t<T>) { ... use T somhow ... }
и вызываете его как Base(std::in_place_type<Derived>) {}
Спасибо! Именно то, что я искал. И даже функция стандартной библиотеки, как предложил @RaymondChen. Спасибо :).
И вы правы. Параметр останется неиспользованным, если его вывести таким образом.
@RaymondChen, твое редактирование не скомпилировалось godbolt.org/z/reWed3bhd
Извини. Зафиксированный. Забыл удалить скобки следа.
Для того, что вы пытаетесь сделать, не существует варианта использования. Вы либо переместите шаблон в сам класс, либо удалите его, поскольку он не может повлиять на построение, если вы не передадите значение/экземпляр/et. ал. Т.
Используя воображаемый синтаксис, мы могли бы представить:
auto bi = <int>B();
auto bf = <float>B();
auto bs = <std::string>B();
Однако все они будут вызывать один и тот же ctor, так что в этом нет смысла.
Поэтому нет смысла включать его в язык, даже если бы мы могли его добавить.
Спасибо за ваш ответ, но у меня определенно есть вариант использования. В конструкторе я вызываю шаблонный метод-член для инициализации моей (как уже указано в комментариях под вопросом) системы поведения с поведением по умолчанию. Для добавления каждого из них требуется свой тип владельца (производный класс), который я должен предоставить внутри конструктора. Надеюсь, теперь это имеет для вас смысл :).
Конструктор вполне может делать что-то другое в зависимости от параметра шаблона, и он может делать это с классом, не являющимся шаблоном. godbolt.org/z/sv949rahz
Связано/обман: Возможно ли иметь шаблон конструктора без параметров? В частности, этот ответ: «Невозможно явно указать аргументы шаблона при вызове шаблона конструктора, поэтому их приходится выводить посредством вывода аргументов».