Я хочу создать кортеж на основе наследования, аналогично тому, как это было сделано в 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
}
Да, вы правы. У меня это работает, если я удалю любой конструктор. Почему они мешают друг другу?
Думаю, я отчасти знаю ответ. Приведение t к значению lvalue, соответствующему типу параметра конструктора, сработало. TupleImpl<Idx + 1, Args...>(static_cast<TupleImpl<Idx + 1, Args...>&>(t)). В противном случае первый конструктор кажется соответствующим после вызова конструктора копирования.





Tuple<int, char, std::string> — это псевдоним TupleImpl<0, int, char, std::string>, который после вычета имеет два конструктора:
TupleImpl(TupleImpl&), который является специализацией первого конструктора, где F равно TupleImpl& и Rest пусто.TupleImpl(TupleImpl&) из второго конструктора.Конструктор №2 выбран потому, что нешаблонные функции предпочтительнее специализаций шаблонов. (Как бы то ни было, это не типичная сигнатура конструктора копирования.)
Этот конструктор вызывает конструктор своей базы TupleImpl<1, char, std::string> со ссылкой на тот же аргумент (который имеет тип производного класса).
После вычета снова есть два конструктора:
TupleImpl(Tuple&), который является специализацией первого конструктора, где F равно Tuple& и Rest пусто.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)...) {}
Ваш код работает, если вы удалите
TupleImpl(TupleImpl& t)