c std::кортеж префикса списка переменных типов

#c #metaprogramming

#c #метапрограммирование

Вопрос:

Я пытаюсь извлечь префикс типов из некоторого списка переменных типов. Вот моя попытка:

 #include <tuple>
#include <type_traits>

template <typename... Ts>
struct pack{};

template <size_t n, typename... Args>
struct prefix_tuple;

template <size_t n, typename... TPrefix, typename Tnext, typename... Ts>
struct prefix_tuple<n, pack<TPrefix...>, Tnext, Ts...>{
    using type = typename 
        prefix_tuple<n-1, pack<TPrefix..., Tnext>, Ts...>::type;
};

template <typename... TPrefix, typename... Ts>
struct prefix_tuple<0, pack<TPrefix...>, Ts...>{
    using type = std::tuple<TPrefix...>;
};

template <size_t n, typename... Args>
using prefix_tuple_t = typename 
    prefix_tuple<n, pack<>, Args...>::type;

bool f(){
    return std::is_same_v<prefix_tuple_t<2, int, char, double>,
                          std::tuple<int, char> >;
}
  

Это завершается ошибкой в gcc 8.2 с:

ошибка: неоднозначное создание экземпляра шаблона для ‘struct prefix_tuple<0, pack< int, char>, double>’

Вторая специализация представляется более конкретной, чем первая, поэтому я не понимаю, почему здесь возникает двусмысленность. Что я делаю не так?

P.S. Это также приводит к сбою в clang 7.0 с аналогичной ошибкой, но, похоже, работает в icc 19.0.1 и msvc 19.16.

Комментарии:

1. Версии gcc / clang работают, если я добавляю специализацию: template <имя типа… TPrefix, имя типа Tnext, имя типа… Ts> struct prefix_tuple<0, pack<TPrefix…>, Tnext, Ts…>{…}; Однако с этим добавлением icc не удается скомпилировать.

Ответ №1:

После некоторых дополнительных исследований, вот мои выводы:

Это правила частичного упорядочения:

1) Если только одна специализация соответствует аргументам шаблона, используется эта специализация.
2) Если совпадает более одной специализации, используются правила частичного порядка, чтобы определить, какая специализация является более специализированной. Используется наиболее специализированная специализация, если она уникальна (если она не уникальна, программа не может быть скомпилирована).
3) Если специализации не совпадают, используется основной шаблон

И:

Неофициально «A более специализирован, чем B» означает «A принимает подмножество типов, которые принимает B«.

Пусть A и B будут первой и второй специализациями в моем коде соответственно. A принимает структуры с числами, n меньшими 0 (чего не делает B). С другой стороны, B принимает структуры с 0 типами, следующими за пакетом префиксов (чего не делает A). Следовательно, ни A, ни B не являются «наиболее специализированными», и программа не должна компилироваться. То есть icc и msvc неверны.


Возможное решение:

Предположим, я добавлю следующую третью специализацию (назовем ее C), упомянутую в моем комментарии:

 template <typename... TPrefix, typename Tnext, typename... Ts>
struct prefix_tuple<0, pack<TPrefix...>, Tnext, Ts...>{
    using type = std::tuple<TPrefix...>;
};
  

C не принимает ни числа, n меньшие 0, ни структуры с 0 типами, следующими за пакетом префиксов. Поэтому он является наиболее специализированным. Кроме того, если n==0 и C нельзя использовать, то не может использоваться и A, так что это устраняет двусмысленность между A и B.

С этим дополнением код работает с gcc, clang и msvc, но icc отклоняет его со следующей ошибкой:

ошибка: более одной частичной специализации соответствует
списку аргументов шаблона класса «prefix_tuple<0UL, pack < int, char >, double>»:
«prefix_tuple<0UL, pack < TPrefix… >, Tnext, Ts…>»
«prefix_tuple<0UL, pack < TPrefix… >, Ts…>»

Как я упоминал ранее, первый из них (C) более специализирован, чем второй (B), поэтому я должен сделать вывод, что icc снова ошибается.

Ответ №2:

В качестве альтернативы вы могли бы использовать std::index_sequence :

 template <typename Seq, typename Tuple> struct prefix_tuple_impl;

template <std::size_t ... Is, typename Tuple>
struct prefix_tuple_impl<std::index_sequence<Is...>, Tuple>
{
    using type = std::tuple<std::tuple_element_t<Is, Tuple>...>;
};

template <std::size_t N, typename ... Ts>
using prefix_tuple_t = typename prefix_tuple_impl<std::make_index_sequence<N>,
                                                  std::tuple<Ts...>>::type;
  

ДЕМОНСТРАЦИЯ