SFINAE приоритет специализации шаблона - PullRequest
0 голосов
/ 23 сентября 2018
#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, decltype(
  std::declval<const T&>().begin(),
  std::declval<const T&>().end(),
  void()
)> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

Я ожидал, что третий шаблон будет более специализированным, чем второй, но я получил неоднозначную реализацию шаблона .

Есть ли способ сделать третий шаблон болееспециализированные?Явная проверка того, является ли T значением std::array во втором шаблоне, не будет работать для меня.Я пишу библиотеку и хотел бы, чтобы пользователи могли определять свои собственные специализации trait.Второй шаблон предназначен для общей специализации для контейнеров в отсутствие более специфической черты.

1 Ответ

0 голосов
/ 23 сентября 2018
#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, std::void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>,void> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

РЕДАКТИРОВАТЬ

Во-первых, нет никаких гарантий, что это действительно больше предположение, чем доказательство.Может быть, кто-то еще может исправить, расширить копировать, вставить его или что-то в этом роде.

Однако мое первое предположение после того, как я увидел вопрос, заключалось в использовании std::void_t.Я почти уверен, что видел что-то подобное раньше, но да, это тоже не гарантирует.Чтобы показать, что std::void_t можно использовать, мы должны показать, что «одна специализация шаблона более специфична, чем другая».И мы делаем это путем проверки частичного заказа.Я подражаю вышесказанному с помощью следующего, который немного короче.

template <typename T, typename SFINAE=void>
struct trait;
//#1
template <typename T>struct trait<T, std::void_t<decltype(std::declval<T>().begin())>>
{
  static const char* name() { return "Container"; }
};
//#2
template <typename T>struct trait<std::vector<T>,void> {
  static const char* name() { return "std::vector"; }
};

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

//#2 from #1: f(trait<std::vector<T>,void>) from f(trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>)
    //P=trait<std::vector<T>,void>
    //A=trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>>
        //P1=std::vector<T>
        //A1=__ANY_TYPE__
        //P2=void
        //A2=std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>
        //==> T=? --> fail, #2 from #1 is not working

Теперь мы должны показать, что # 1 из # 2 работает.Если так, то мы показали, что №2 более специализирован.

//#1 from #2: f(trait<T, std::void_t<decltype(std::declval<T>().begin())>>) from f(trait<std::vector<__ANY_TYPE__>,void>)
    //P=trait<T, std::void_t<decltype(std::declval<T>().begin())>>
    //A=trait<std::vector<__ANY_TYPE__>,void>
        //P1=T
        //A1=std::vector<__ANY_TYPE__>
        //P2=std::void_t<decltype(std::declval<T>().begin())> //(*)
        //A2=void
        //==> T=std::vector<__ANY_TYPE__> ok #1 from #2 works

Это в основном мой набросок без проверки стандарта или чего-либо еще.Я почти уверен, что вы можете найти его где-нибудь в бесконечных строках стандарта ...

Если вы обратили внимание, вы заметили (*).Эта строка в основном единственная важная, если вы хотите использовать decltype (...).Я предполагаю, что использование decltype (...) приводит к невыгружаемому контексту для правой стороны, который, возможно, не позволяет использовать T из вывода P1 / A1.Но да, это в основном причина, почему я не включил ответ сначала в рабочее std::void_t решение.И, наконец, альтернативное определение std::void_t с typename ... я думаю, что невыбранный контекст слишком похож на decltype (...) из-за части typename.


EDIT

Просто чтобы добавить несколько заключительных строк.В принципе не должно быть проблем с sfinae decltype.Хорошо, его не выводимый контекст, но почему это проблема?Единственное, о чем я могу подумать, это то, что в невыбранном контексте есть некоторые особые правила в сочетании с частичным упорядочением ...

...