3

currently I'm working on a dynamic container structure, which represents one pod value or has vector of pointers with same container type. The container has an interface optional<T> expect_value<T>() 。 For pod types the implemention is simple. For the non pod value, I would call expect_value<tuple<args...>>(), the args would be tuple as well. But when implement this function, I come across a trouble: how to redirect a.expect_value<tuple<args...>>() to a.expect_value_tuple<args...>>(). For example, the call to a.expect_value<tuple<int,int>() would return the result of a.expect_value_tuple<int, int>(). Because the argument is empty, I cant use the type deduce of unpacked arguments. Then the whole project just cant progress any more. Any ideas? Below is the minimal example for my intention.

#include <tuple>
#include <vector>
#include <optional>
#include <functional>

using namespace std;

template<typename T>
struct is_tuple_impl : std::false_type {};

template<typename... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};

template<typename T>
struct is_tuple : is_tuple_impl<std::decay_t<T>> {};

class my_container;

template<typename... args, size_t... arg_idx>
optional<tuple<args>...> get_tuple_value_from_vector(const vector<my_container*>& v_list, std::index_sequence<arg_idx...>)
{
    auto temp_result = make_tuple((*v_list[arg_idx]).expect_value<arg>()...);

    if(!(get<arg_idx>(temp_result) &&...))
    {
        return nullopt;
    }
    return make_tuple(get<arg_idx>(temp_result).value()...);

}

class my_container
{
public:
    int value_type; // 1 for v_int 2 for v_list 0 empty
    union
    {
        int v_int;
    };
    vector<my_container*> v_list;
    template<typename T> 
    optional<T> expect_simple_value();
    template<typename... args>
    optional<tuple<args...>> expect_tuple_value();
    template<typename T> 
    optional<T> expect_value();
};
template <typename T>
optional<T> my_container::expect_simple_value()
{
    return nullopt;
}

template <>
optional<int> my_container::expect_simple_value()
{
    if(value_type == 1)
    {
        return v_int;
    }
    return nullopt;
}

template<typename... args>
optional<tuple<args...>> my_container::expect_tuple_value()
{
    if(v_list.size() == 0)
    {
        return nullopt;
    }
    for(const auto i: v_list)
    {
        if(!i)
        {
            return nullopt;
        }
    }
    auto the_tuple_size = sizeof...(args);
    if(v_list.size() != the_tuple_size)
    {
        return nullopt;
    }
    return get_tuple_value_from_vector<args...>(v_list, index_sequence_for<args...>{});
}
template <typename T>
optional<T> my_container::expect_value()
{
    if(is_tuple<T>::value)
    {
        return expect_tuple_value<T>();
    }
    else
    {
        return expect_simple_value<T>();
    }
}

int main()
{
    my_container test_value;
    test_value.value_type = 1;
    test_value.v_int = 1;
    auto result = test_value.expect_value<tuple<int, int>>();
    if(result)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

the heart of the problem is the line return expect_tuple_value<T>(); When logic goes there, the T should be tuple<args...>, but what I want is return return expect_tuple_value<args...>().

spiritsaway
  • 635
  • 1
  • 7
  • 16

3 Answers3

2

What about using template argument deduction and overload resolution through partial ordering of function template:

class my_container
{

public:
    template<class T> optional<T> expect_value_simple();

    template<class...Args> optional<tuple<Args...>> expect_value_tuple();


private:
    template<class T> struct deduce_type{};

    template<typename T> 
    auto expect_value_dispatching(deduce_type<T>){
       return expect_value_simple<T>();
       }
    template<typename...Args>
    auto expect_value_dispatching(deduce_type<tuple<Args...>>){
       return expect_value_tuple<Args...>();
       }
public:     
    template<typename T> 
    auto expect_value(){
        return expect_value_dispatching(deduce_type<T>{});
        }
};

(Demo)

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Ah, this is even better than what I was tinkering on. I agree, we don't need to tag-dispatch each individual parameter, just a monostate type that we can pass as actual argument for type deduction. This elegantly does the `constexpr if` and pack deduction at once, but it's not immediately obvious to onlookers why (or when) the variadic version wins. – Max Langhof Dec 12 '18 at 14:56
  • You do need such an intermediary function for every instance where you want to go back from `T` to `Args...` (somewhat obviously), so Yakk's lambda version wins a bit on reusability. If templated variadic lambdas had [better support](https://godbolt.org/z/b4bpdi) we could combine both. – Max Langhof Dec 12 '18 at 15:03
  • 1
    This answer is simpler to reason than the lambda version. – spiritsaway Dec 12 '18 at 15:45
1

The if before the line in question should be a constexpr if.

Unpacking of types is annoying to do without using a class helper. I can do it with some fancy lambda action tho.

template<class T>
struct tag_t{using type=T;};
template<class Tag>
using type=typename Tag::type;

template<class Tuple>
struct unpack_tuple;
template<class...Ts>
struct unpack_tuple<std::tuple<Ts...>> {
  template<class F>
  decltype(auto) operator()(F&& f)const {
    return std::forward<F>(f)( tag_t<Ts>{}... );
  }
};
#define TYPE_FROM(...) \
  type< std::decay_t<decltype(__VA_ARGS__)> >

then we get

if constexpr(is_tuple<T>::value)
{
    return unpack_tuple<T>{}([&](auto...tags){
      return expect_tuple_value<TYPE_FROM(tags)...>();
    });
}
else
{
    return expect_simple_value<T>();
}

and done.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wouldn't a templated lambda with variadic template arguments be a possibility too (ignoring all ridiculousness of such a construct)? Or would there be a conflict in having to specify the types for the specialization outside of `unpack_tuple::operator()` already? – Max Langhof Dec 12 '18 at 14:37
  • @MaxLanghof I'm not so aggressive as to use C++17 or 20 by default quite yet. :) – Yakk - Adam Nevraumont Dec 12 '18 at 15:02
1

The core issue here is that you need to do argument deduction at least once to go back from a type to its variadic parameters. To do that, you must pass some instance of such a variadically-templated type to a function - but it does not have to be the original one.

Yakk's solution does this via a variadic lambda that is passed instances of tag-types (one per tuple type). The advantage here is that you can use a lambda instead of an explicit intermediary function every time.

Oliv's solution uses a monostate type which we can instantiate and pass to a function for type deduction. It's much cleaner but requires such an intermediary function for every use case.

Here is a (more or less theoretical) version combining both, using templated variadic lambdas (C++20, and they apparently don't even have clang support as of now):

template<class... Args>
struct MonostateTuple
{};

template<class... Args>
auto tupleToMonostate(std::tuple<Args...>)
{
    return MonostateTuple<Args...>{};
}

template<class T, class F>
auto unpack_tuple(F&& f)
{
    using MT = decltype(tupleToMonostate(std::declval<T>()));
    return std::forward<F>(f)(MT{});
}

/// User code

template<class Tuple>
auto foo()
{
    return unpack_tuple<Tuple>([&] <typename... Args> (MonostateTuple<Args...>) {
        return expect_tuple_value<Args...>();
    });
}

It's a bit more ugly in the lambda signature (not to mention the lack of compiler support again) but theoretically combines both advantages.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • First interesting template lambda use case I see! – Oliv Dec 12 '18 at 15:12
  • @Oliv It appears that the clang people agree with a lack of interesting use cases ;) – Max Langhof Dec 12 '18 at 15:12
  • I feel GCC has taken a big lead in the last 2 years over Clang, not only on experimental new feature implementation but also on the quality of implementation of standardized one. This attitude may play a role in that. – Oliv Dec 12 '18 at 15:43
  • 1
    In few hours, while coding with template lambda in mind, I found interesting use cases of template lambda that improve readability: https://godbolt.org/z/xAJ9Up. It avoids the distracting delctype(arg) and sometime some type manipulations. – Oliv Dec 13 '18 at 14:00