Кортеж на основе наследования с конструктором копирования

Я хочу создать кортеж на основе наследования, аналогично тому, как это было сделано в https://stackoverflow.com/a/52208842/1284735, за исключением конструкторов.

Я думаю, что мой обычный конструктор работает нормально, но когда я пытаюсь использовать конструктор копирования, компилятор говорит, что я не предоставляю функции никаких аргументов. Что мне не хватает?

tuple.cpp:41:5: примечание: конструктор-кандидат нежизнеспособен: требуется один аргумент «t», но аргументы не предоставлены ТуплеИмпл(ТуплеИмпл& т):

template<size_t Idx, typename T>
struct TupleLeaf {
    T val;
    TupleLeaf() = default;
    // I think should also have proper constructors but left like this for simplicity
    TupleLeaf(T t): val(t) {} 
};

template<size_t Idx, typename... Args>
struct TupleImpl;

template<size_t Idx, typename T, typename... Args>
struct TupleImpl<Idx, T, Args...>: TupleLeaf<Idx, T>, TupleImpl<Idx + 1, Args...> {
    template<typename F, typename... Rest>
    TupleImpl(F&& val, Rest&&... args): TupleLeaf<Idx, T>(std::forward<F>(val)), TupleImpl<Idx + 1, Args...>(std::forward<Rest>(args)...) {}

    TupleImpl(TupleImpl& t): 
        TupleLeaf<Idx, T>(static_cast<TupleLeaf<Idx, T>>(t).val),
        TupleImpl<Idx + 1, Args...>(t) {}
};

template<size_t Idx>
struct TupleImpl<Idx> {
    TupleImpl() = default;
    TupleImpl(TupleImpl<Idx> &t) {}
};

template<typename... Args>
using Tuple = TupleImpl<0, Args...>;

int main() {
    Tuple<int, char, string> tup{1, 'a', "5"}; // Works okay
    Tuple<int, char, string> x = tup; // Fails here
}

Ваш код работает, если вы удалите TupleImpl(TupleImpl& t)

LHLaurini 10.04.2024 23:59

Да, вы правы. У меня это работает, если я удалю любой конструктор. Почему они мешают друг другу?

petabyte 11.04.2024 00:37

Думаю, я отчасти знаю ответ. Приведение t к значению lvalue, соответствующему типу параметра конструктора, сработало. TupleImpl<Idx + 1, Args...>(static_cast<TupleImpl<Idx + 1, Args...>&>(t)). В противном случае первый конструктор кажется соответствующим после вызова конструктора копирования.

petabyte 11.04.2024 01:18
Стоит ли изучать 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
3
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Tuple<int, char, std::string> — это псевдоним TupleImpl<0, int, char, std::string>, который после вычета имеет два конструктора:

  1. TupleImpl(TupleImpl&), который является специализацией первого конструктора, где F равно TupleImpl& и Rest пусто.
  2. TupleImpl(TupleImpl&) из второго конструктора.

Конструктор №2 выбран потому, что нешаблонные функции предпочтительнее специализаций шаблонов. (Как бы то ни было, это не типичная сигнатура конструктора копирования.)

Этот конструктор вызывает конструктор своей базы TupleImpl<1, char, std::string> со ссылкой на тот же аргумент (который имеет тип производного класса).

После вычета снова есть два конструктора:

  1. TupleImpl(Tuple&), который является специализацией первого конструктора, где F равно Tuple& и Rest пусто.
  2. TupleImpl(TupleImpl&)

Поскольку аргумент имеет тип Tuple, который в данном контексте является производным от TupleImpl, первый вариант подходит лучше.

Затем этот конструктор вызывает конструктор своей базы Tuple<2, std::string> с перенаправленными Rest аргументами, который представляет собой пустой пакет параметров. Он пытается создать конструкцию по умолчанию Tuple<2, std::string>, но у него нет конструктора по умолчанию.

В этом смысл ошибки: оба конструктора Tuple<2, std::string> требуют один аргумент, а мы передаем ноль.

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

template<typename F, typename... Rest>
  requires (!std::derived_from<std::decay_t<F>, TupleImpl>)
TupleImpl(F&& val, Rest&&... args): TupleLeaf<Idx, T>(std::forward<F>(val)), TupleImpl<Idx + 1, Args...>(std::forward<Rest>(args)...) {}

https://godbolt.org/z/5bxMbM5Yc

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