3

Base Problem

The base problem I'm trying to solve is this:

I have a template parameter pack ArgTypes and I need to make a tuple with each of the types wrapped in std::optional. For example: make_optional_tuple<int, double, std::string> should return a tuple of tuple of type std::tuple<std::optional<int>, std::optional<double>, std::optional<std::string>> with each element in the tuple initialized to std::nullopt.

My work so far

I'm using g++ included with GCC 7.1. I've been wrestling with the C++ type system for quite a while and I have code that works with one type but not multiple types. The error I get with multiple types in the parameter pack is:

error: unable to deduce 'auto' from 'tuple_cat<std::make_tuple(_Elements&& ...) [with _Elements = {std::optional<int>&}](), optional_tuple>'

Does anyone know how I can fix this? Intuitively (though I may be incorrect) I think the problem is that the C++ type system is unable to deduce the type of auto optional_tuple as it involves fully resolving the chain of recursions of the different function templates produced by the parameter pack -- something that perhaps the type system is unable to do when it is trying to resolve the type of auto variables.

Here is a minimal working example:

#include <optional>
#include <tuple>

template<int n, typename ArgType, typename... ArgTypes>
struct make_optional_tuple_helper {
    static auto tuple() {
        std::optional<ArgType> optional_arg = {};
        auto optional_tuple = make_optional_tuple_helper<n-1, ArgTypes...>::tuple();
        return std::tuple_cat<std::make_tuple(optional_arg), optional_tuple>;
    }
};

template<typename ArgType>
struct make_optional_tuple_helper<1, ArgType> {
    static std::tuple<std::optional<ArgType>> tuple() {
        std::optional<ArgType> optional_arg = {};
        return std::make_tuple(optional_arg);
    }
};

template<typename... ArgTypes>
auto make_optional_tuple() {
    return make_optional_tuple_helper<std::tuple_size<std::tuple<ArgTypes...>>::value, ArgTypes...>::tuple();
};

int main() {
    auto i = make_optional_tuple<int>(); // works!
    auto j = make_optional_tuple<int, double>(); // error: unable to deduce 'auto'...
}

(Compile with g++-7 -std=c++1z example.cpp)

Thanks for your time and/or help!

Community
  • 1
  • 1
user3831357
  • 149
  • 1
  • 7

2 Answers2

3

You are way overthinking this:

template<typename... ArgTypes>
auto make_optional_tuple() {
    return std::tuple<std::optional<ArgTypes>...>{};
}

Since a default-constructed optional is nullopt, that's all you need.


Your specific problem is you used the wrong brackets:

return std::tuple_cat<std::make_tuple(optional_arg), optional_tuple>;
                    ~~~                                           ~~~

Those should be parentheses. As-is, you're returning a pointer to an ill-formed function template specialization instead of a tuple.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for your answer. Indeed that was the issue. I would say not so much "overthinking," more like "not knowledgable enough about parameter packs". For certain though, what you have written here is much cleaner than the mess that I have. I'm trying to understand the syntax here -- specifically the `std::optional...` part. All the examples I have seen have the ellipses immediately following the template argument name, like `ArgTypes...`. Would you mind if I asked you to explain the syntax a little more? – user3831357 Jun 22 '17 at 22:58
  • @user3831357 [my answer on that subject](https://stackoverflow.com/a/26767333/2069064) – Barry Jun 22 '17 at 22:59
-2

It will not work because the return type of the function is deduced on the first return, and you are trying to call the function before the first return.

I believe you could do something long the lines of:

template<typename... ArgTypes>
struct make_optional_tuple_helper {
    static auto tuple() {
        return std::make_tuple(std::optional<ArgTypes>()...);
    }
};

Or

template<typename... ArgTypes>
struct make_optional_tuple_helper {
    static auto tuple() {
        return std::tuple<std::optional<ArgTypes>...>();
    }
};
lapinozz
  • 190
  • 2
  • 6
  • Thanks for your answer. Can you elaborate what you mean by 'deduced on the first return'? The way that I understand it is that the way that the recursion is set up is such that the call stack is traversed all the way to the final template specialization (i.e. `make_optional_tuple_helper<1, ArgType>`) before any statements are returned; and the return type of this base case is deducible since the first example (with one type in the parameter pack) works. – user3831357 Jun 22 '17 at 22:35
  • Right, i missed that – lapinozz Jun 22 '17 at 22:42
  • I see it, you are calling tuple_cat<> rather than tuple_cat() – lapinozz Jun 22 '17 at 22:42