Шаблонный конструктор абстрактного класса без вывода типа параметра в C++

Я хочу, чтобы мой абстрактный базовый класс имел шаблонный конструктор, но не могу заставить его работать без дополнительного параметра, из которого можно вычесть тип.

Я хочу сделать что-то вроде этого:

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>() (при условии, что база не является абстрактной), неясно, для чего предназначен указанный шаблон. Шаблон класса или шаблон функции конструктора.)

Любая помощь будет принята с благодарностью! :)

Связано/обман: Возможно ли иметь шаблон конструктора без параметров? В частности, этот ответ: «Невозможно явно указать аргументы шаблона при вызове шаблона конструктора, поэтому их приходится выводить посредством вывода аргументов».

Remy Lebeau 24.07.2024 19:49
template <typename T> Base() — бесполезный конструктор. Вы не можете вызвать конструктор, поэтому невозможно указать, что такое T.
NathanOliver 24.07.2024 19:50

Базовый класс производного класса должен быть классом шаблона, но вы определили шаблонный конструктор (где шаблон должен использоваться только для аргументов конструктора).

S. Gleissner 24.07.2024 19:52

@S.Gleissner, с другой стороны, шаблонирование самого базового класса затруднит, если не сделает невозможным, создание нескольких производных классов с общим базовым классом, в зависимости от того, как они используются.

Remy Lebeau 24.07.2024 19:54

@NathanOliver Ну, вот в чем дело, я хочу вызвать его из производного класса и определить T там, чтобы использовать его в упомянутом вами конструкторе базового класса.

UniCode 24.07.2024 19:56

@S.Gleissner Ответ Реми Лебо на ваш комментарий также является причиной того, что я не использую шаблон класса. Кроме того, поскольку мне это нужно только в конструкторе.

UniCode 24.07.2024 19:59

Если вы заставите конструктор принимать параметр T, производный класс сможет передать его в базу. Derived() : Base(T{}) {}

BoP 24.07.2024 19:59

@UniCode Это твоя проблема. Вы никогда не вызываете конструктор. Это не то, что вы можете сделать. Вам нужно либо передать конструктору параметр типа T, чтобы его можно было вывести при использовании, либо создать Base шаблон класса.

NathanOliver 24.07.2024 20:04

@BoP: std::type_identity<T> даже лучше.

Jarod42 24.07.2024 20:28

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

Ted Lyngmo 24.07.2024 20:43

@TedLyngmo Ну, я пишу систему поведения для элементов пользовательского интерфейса в моем (своего рода) движке, и я хочу, чтобы каждая база элементов была инициализирована с одинаковым поведением по умолчанию. Для этой цели у меня есть метод initBehaviors(), который добавляет все поведения к объекту BehaviorManager, который есть у каждого элемента пользовательского интерфейса. Для добавления мне нужно знать владельца, а также параметры каждого поведения. И вот для чего мне это нужно. Я хочу, чтобы владелец всегда имел производный тип, но просто не знал об этом в базовом классе + я хочу всегда инициализировать поведение по умолчанию, поэтому оно должно идти в конструкторе

UniCode 24.07.2024 21:34

@TedLyngmo Надеюсь, вы хоть немного поняли, но важно отметить, что это решение будет не единственным, которое я вижу, но точно единственным чистым :). По сути, я просто хочу иметь возможность делать Button(...) : UiElement<Button> {} (например) в каждом элементе пользовательского интерфейса, что все еще немного избыточно, но самое чистое, что я могу придумать на данный момент.

UniCode 24.07.2024 21:36

в чем именно вопрос? Что плохого в использовании параметра для определения типа?

463035818_is_not_an_ai 24.07.2024 23:18

@Jarod42 К сожалению, std::type_identity — это черта трансформации, которая не обязательно должна быть конструктивной. Вместо этого используйте std::in_place_type.

Raymond Chen 25.07.2024 01:53

@UniCode Я рад, что ты получил ответ. Для меня это стало похоже на вопрос XY.

Ted Lyngmo 25.07.2024 04:39

@TedLyngmo Да, я понимаю, что вы имеете в виду, но даже помимо моего варианта использования мне все равно хотелось бы знать, возможно ли это и как это возможно или нет. Просто для лучшего понимания C++ в целом.

UniCode 25.07.2024 14:55
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
16
87
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Я полагаю, вы не хотите передавать параметр типа 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>) {}

Raymond Chen 25.07.2024 01:52

Спасибо! Именно то, что я искал. И даже функция стандартной библиотеки, как предложил @RaymondChen. Спасибо :).

UniCode 25.07.2024 02:16

И вы правы. Параметр останется неиспользованным, если его вывести таким образом.

UniCode 25.07.2024 02:25

@RaymondChen, твое редактирование не скомпилировалось godbolt.org/z/reWed3bhd

463035818_is_not_an_ai 26.07.2024 16:51

Извини. Зафиксированный. Забыл удалить скобки следа.

Raymond Chen 26.07.2024 17:10

Для того, что вы пытаетесь сделать, не существует варианта использования. Вы либо переместите шаблон в сам класс, либо удалите его, поскольку он не может повлиять на построение, если вы не передадите значение/экземпляр/et. ал. Т.

Используя воображаемый синтаксис, мы могли бы представить:

auto bi = <int>B();
auto bf = <float>B();
auto bs = <std::string>B();

Однако все они будут вызывать один и тот же ctor, так что в этом нет смысла.

Поэтому нет смысла включать его в язык, даже если бы мы могли его добавить.

Спасибо за ваш ответ, но у меня определенно есть вариант использования. В конструкторе я вызываю шаблонный метод-член для инициализации моей (как уже указано в комментариях под вопросом) системы поведения с поведением по умолчанию. Для добавления каждого из них требуется свой тип владельца (производный класс), который я должен предоставить внутри конструктора. Надеюсь, теперь это имеет для вас смысл :).

UniCode 25.07.2024 02:22

Конструктор вполне может делать что-то другое в зависимости от параметра шаблона, и он может делать это с классом, не являющимся шаблоном. godbolt.org/z/sv949rahz

463035818_is_not_an_ai 26.07.2024 12:51

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