как перенаправить типы кортежа для специализации другого шаблона?

в настоящее время я работаю над динамической контейнерной структурой, которая представляет одно значение модуля или имеет вектор указателей с тем же типом контейнера. Контейнер имеет интерфейс optional<T> expect_value<T>()。 Для типов подов реализация проста. Для значения, отличного от pod, я бы назвал expect_value<tuple<args...>>(), args также будет кортежем. Но при реализации этой функции сталкиваюсь с проблемой: как перенаправить a.expect_value<tuple<args...>>() на a.expect_value_tuple<args...>>(). Например, вызов a.expect_value<tuple<int,int>() вернет результат a.expect_value_tuple<int, int>(). Поскольку аргумент пуст, я не могу использовать вывод распакованных аргументов. Тогда весь проект просто не может больше развиваться. Любые идеи? Ниже приведен минимальный пример моего намерения.

#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;
    }
}

Суть проблемы - это строка return expect_tuple_value<T>(); Когда логика идет туда, T должно быть tuple<args...>, но я хочу вернуть return expect_tuple_value<args...>().


person spiritsaway    schedule 12.12.2018    source источник
comment
Не связаны, но предпочитают использовать enum class вместо int для int value_type.   -  person Max Langhof    schedule 12.12.2018


Ответы (3)


Как насчет использования вывода аргументов шаблона и разрешения перегрузки посредством частичного упорядочивания шаблона функции:

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>{});
        }
};

(Демо)

person Oliv    schedule 12.12.2018
comment
Ах, это даже лучше, чем то, над чем я возился. Я согласен, нам не нужно отправлять теги для каждого отдельного параметра, только тип моносостояния, который мы можем передать в качестве фактического аргумента для вывода типа. Это элегантно делает constexpr if и вычитание пакетов одновременно, но для наблюдателей не сразу очевидно, почему (или когда) вариативная версия выигрывает. - person Max Langhof; 12.12.2018
comment
Вам действительно нужна такая посредническая функция для каждого случая, когда вы хотите вернуться с T на Args... (несколько очевидно), поэтому лямбда-версия Yakk немного выигрывает по возможности повторного использования. Если бы у шаблонных вариативных лямбда-выражений была лучшая поддержка, мы могли бы объединить оба. - person Max Langhof; 12.12.2018
comment
Этот ответ проще рассуждать, чем лямбда-версия. - person spiritsaway; 12.12.2018

if перед рассматриваемой строкой должен быть constexpr if.

Распаковка типов утомляет без использования помощника класса. Я могу сделать это с помощью необычных c ++ 14 лямбда-действие 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__)> >

тогда мы получаем

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>();
}

и готово.

person Yakk - Adam Nevraumont    schedule 12.12.2018
comment
Разве шаблонная лямбда с вариативными шаблонными аргументами тоже не возможна (игнорируя всю нелепость такой конструкции)? Или возникнет конфликт из-за необходимости уже указывать типы для специализации вне unpack_tuple::operator()? - person Max Langhof; 12.12.2018
comment
@MaxLanghof Я пока не настолько агрессивен, чтобы использовать C ++ 17 или 20 по умолчанию. :) - person Yakk - Adam Nevraumont; 12.12.2018

Основная проблема здесь в том, что вам нужно сделать вывод аргументов хотя бы один раз, чтобы вернуться от типа к его вариативным параметрам. Для этого вы должны передать некоторый экземпляр такого типа с вариативным шаблоном в функцию, но он не обязательно должен быть исходным.

Решение Yakk делает это с помощью переменной лямбды, которой передаются экземпляры типов тегов (по одному на тип кортежа). Преимущество здесь в том, что вы можете каждый раз использовать лямбда вместо явной промежуточной функции.

В решении Oliv используется тип моносостояния, который мы можем создать и передать функции для вывода типа. Он намного чище, но требует такой посреднической функции для каждого варианта использования.

Вот (более или менее теоретическая) версия, объединяющая оба, с использованием шаблонных вариативных лямбда-выражений (C ++ 20, и у них, по-видимому, даже нет поддержка clang на данный момент):

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...>();
    });
}

В лямбда-сигнатуре это немного уродливее (не говоря уже об отсутствии поддержки компилятора), но теоретически объединяет оба преимущества.

person Max Langhof    schedule 12.12.2018
comment
Первый интересный пример использования лямбда-выражения, который я вижу! - person Oliv; 12.12.2018
comment
@Oliv Похоже, что люди clang согласны с отсутствием интересных вариантов использования;) - person Max Langhof; 12.12.2018
comment
Я чувствую, что GCC за последние 2 года стал большим лидером по сравнению с Clang не только в экспериментальной реализации новой функции, но и в качестве реализации стандартной. Такое отношение может сыграть в этом свою роль. - person Oliv; 12.12.2018
comment
За несколько часов, при кодировании с использованием лямбда-выражения шаблона, я обнаружил интересные варианты использования лямбда-выражения шаблона, которые улучшают читаемость: godbolt. org / z / xAJ9Up. Это позволяет избежать отвлекающего delctype (arg) и иногда некоторых манипуляций с типами. - person Oliv; 13.12.2018