2

I have derived from std::tuple and but was unable construct the derived class from an initializer list due to issues with class template argument deduction. Is there a better way to construct such a class beyond just giving it an already constructed tuple first{ std::tuple{1, 1.0f, 1u} };.

template <typename T, typename... Types>
struct first : public std::tuple<T, Types...>
{
    //using std::tuple<T, Types...>::tuple;
    template<typename F>
        requires std::invocable<F, T>
    auto transform(F f)
    {
        auto result = *this;
        std::get<0>(result) = f(std::get<0>(result));
        return result;
    }
};

int main()
{
    //auto tuple = first{ 1, 1.0f, 1u };
    auto tuple = first{ std::tuple{1, 1.0f, 1u} };
    auto tuple2 = tuple.transform([](auto a) {return a + 3; });
}
Tom Huntington
  • 2,260
  • 10
  • 20
  • Related to [class-template-argument-deduction-failed-with-derived-class](https://stackoverflow.com/questions/46894136/class-template-argument-deduction-failed-with-derived-class) and [how-to-make-argument-deduction-work-for-derived-class-which-using-base-class-constructor](https://stackoverflow.com/questions/61311297/how-to-make-argument-deduction-work-for-derived-class-which-using-base-class-con) – Jarod42 Jul 28 '22 at 08:41

2 Answers2

2

I can't exactly tell you why the using std::tuple<T, Types...>::tuple; directive doesn't work here, but everything seems to work fine, if you simply define the constructors for taking the parameters T, Types... and for taking a std::tuple<T, Types...> manually:

template <typename T, typename... Types>
struct first : public std::tuple<T, Types...>
{
    first(T&& t, Types&&... vals)
        : std::tuple<T, Types...>{t, std::forward<Types>(vals)...}
    {}

    first(std::tuple<T, Types...>&& t)
        : std::tuple<T, Types...>(t)
    {}

    template<typename F>
        requires std::invocable<F, T>
    auto transform(F f)
    {
        auto result = *this;
        std::get<0>(result) = f(std::get<0>(result));
        return result;
    }
};

int main()
{
    auto tuple1 = first{1, 1.0f, 1u};
    auto tuple2 = first{std::tuple{1, 1.0f, 1u}};
    auto tuple3 = tuple2.transform([](auto a) {return a + 3; });

    std::cout << std::get<0>(tuple1) << ", " << std::get<1>(tuple1) << ", " << std::get<2>(tuple1) << std::endl;
    std::cout << std::get<0>(tuple3) << ", " << std::get<1>(tuple3) << ", " << std::get<2>(tuple3) << std::endl;
}

The output is:

1, 1, 1
4, 1, 1

This is what you intended?

Patrick Roocks
  • 3,129
  • 3
  • 14
  • 28
  • 1
    With `first(T&& t, Types&&... vals)`, you only expect rvalue reference (unless types are specified for lvalue reference). `int i =42; first{i};` won't compile. – Jarod42 Jul 28 '22 at 08:29
  • *"I can't exactly tell you why [..]"* See linked question, CTAD is not inherited. – Jarod42 Jul 28 '22 at 08:42
2

Inherited constructors are not part of CTAD.

You might replicate std::tuple's CTAD:

template <typename T, typename... Types>
first(T, Types...) -> first<T, Types...>;

// auto tuple = first{1, 1.0f, 1u}; // is ok now
// auto tuple = first{ std::tuple{1, 1.0f, 1u} }; // broken now

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302