Любой способ замены шаблона до поиска, зависящего от аргумента (или обходные пути?)

Я предполагаю, что основная предпосылка этого вопроса заключается в том, что я пытаюсь использовать enable_if вместе с поиском, зависящим от аргумента (ADL), но я не уверен, возможно ли это. Я вижу на этой странице, что

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

Так что я полагаю, что это не сработает, но в духе обучения я хотел бы задать вопрос.

Вот пример того, что я пытаюсь сделать:

#include <iostream>

namespace lib1 {
  template <typename T>
  void archive(T & t)
  {
    serialize(t);
  }
}

namespace lib2 {
struct VectorInt {
  int x;
  int y;
};

struct VectorDouble {
  double x;
  double y;
};

template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
  std::cout << vect.x << std::endl;
}

// maybe do something different with VectorDouble. Overloading would work,
// but I'm curious if it can be made to work with enable_if

}

int main() {
  lib2::VectorInt myvect;
  myvect.x = 2;
  lib1::archive(myvect);
}

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

Во всяком случае, попытка скомпилировать это дает сообщение «ошибка: переменная или поле« сериализация »объявлены недействительными».

Насколько я понимаю, это не сработает, потому что enable_if оценивается только после поиска, зависящего от аргумента? Это правильно?

Для тех, кто хочет поиграть с этим, у меня есть код на repl.it: https://repl.it/repls/HalfBlandJumpthreading


person Nickolai    schedule 17.03.2018    source источник
comment
T не подлежит вычету и не предоставляется для serialize.   -  person Jarod42    schedule 17.03.2018
comment
Хотите этого?   -  person Jarod42    schedule 17.03.2018
comment
Что такое T в вашей основной функции? вектор‹int›? Я предполагаю, что в этом случае вам нужно изменить подпись?   -  person WhatABeautifulWorld    schedule 17.03.2018
comment
Перегрузка void serialize(VectorInt& vect) кажется проще.   -  person Jarod42    schedule 17.03.2018
comment
@WhatABeautifulWorld Это VectorInt, извините, код изменился, но не весь!   -  person Nickolai    schedule 17.03.2018
comment
@ Jarod42 В этом случае да, однако мой реальный вариант использования немного сложнее. Я хотел бы реализовать serialize таким же образом для пары типов с одинаковыми переменными-членами, но по-разному для других типов. Надеюсь, это имеет смысл.   -  person Nickolai    schedule 17.03.2018
comment
@ Jarod42 Re: ваш пример: ... Наверное, да? У меня были проблемы с аргументами по умолчанию, но, похоже, это работает с вашим примером. Однако я не понимаю, почему он работает с void*, а не с T вместо типа enable_if? Я также не понимаю, почему это вообще работает, поскольку второй аргумент шаблона не используется?   -  person Nickolai    schedule 17.03.2018
comment
Вы почти никогда не захотите использовать enable, если таким образом. Пропустите простой const T & и используйте второй параметр шаблона по умолчанию. Ваша проблема не имеет ничего общего с adl, как только вы передаете VectorInt, выполняется поиск пространства имен, и он работает нормально. Проблема в том, что вы отключили вывод шаблона тем, как вы объявляете параметр.   -  person Nir Friedman    schedule 17.03.2018
comment
На самом деле ссылка Jarod42s уже мертва, я предлагаю изучить ее и немного прочитать о том, когда параметр шаблона может и не может быть выведен.   -  person Nir Friedman    schedule 17.03.2018
comment
@NirFriedman Спасибо за ответ. Если бы вы могли разработать более разобранный ELI5, это действительно помогло бы! Я довольно смущен тем, как работает второй параметр по умолчанию. Я вижу, как он работает с кодом Jarod42, но не понимаю, как/почему.   -  person Nickolai    schedule 17.03.2018
comment
Я думаю, так работает SFINAE? Когда он пытается оценить этот шаблон функции с помощью VectorDouble, подстановка терпит неудачу, и компилятор пытается найти ::type внутри структуры enable_if, но она не существует, потому что условие было ложным, и поэтому он просто записывает это как подстановку. неудача и движение дальше?   -  person Nickolai    schedule 17.03.2018


Ответы (1)


В вашем примере происходят две отдельные вещи: есть (функция) вывод аргумента шаблона и есть поиск, зависящий от аргумента (ADL). Отношения между ними немного сложны, если вы попытаетесь явно указать параметры шаблона (эй, это C++), вы можете прочитать больше здесь: http://en.cppreference.com/w/cpp/language/adl (в разделе примечаний).

Тем не менее, в целом в C++ обычно лучше разрешить шаблонам функций выводить свои аргументы, а не указывать их явно, что вы все равно пытались сделать здесь, так что все в порядке.

Когда вы делаете:

namespace lib1 {
  template <typename T>
  void archive(T & t)
  {
    serialize(t);
  }
}

Вызов serialize подходит для ADL, и, поскольку он зависит от t, он откладывается до создания экземпляра шаблона, поскольку требуется тип t (это называется двухфазным поиском). Когда вы вызываете archive с объектом типа VectorInt, вызов serialize будет искать в пространстве имен VectorInt. Все работает просто отлично. Проблема в этом коде:

template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
  std::cout << vect.x << std::endl;
}

Вы не указали параметры шаблона явно, поэтому их нужно вывести. Но форма, которую вы здесь даете, не допускает вывода: http://en.cppreference.com/w/cpp/language/template_argument_deduction, см. невыведенные контексты, самый первый пример. Чтобы попытаться лучше понять почему, подумайте, что вы просите сделать компилятор: вы передаете VectorInt и просите компилятор найти T такое, что std::enable_if<std::is_same<T, VectorInt>::value, T>::type> оказывается VectorInt. Это кажется разумным, потому что интуитивно enable_if — это просто оператор идентификации (для типов), если первый аргумент истинен. Но компилятор не имеет специальных знаний о enable_if. Это равносильно высказыванию: найдите T такое, что Foo<T>::type равно Bar. У компилятора нет способа сделать это, не создавая экземпляр Foo для каждого отдельного T, что невозможно.

Мы хотим использовать enable_if, но не таким образом, чтобы отключить дедукцию. Обычно лучше всего использовать enable_if в параметре шаблона по умолчанию:

template<typename T, typename U = typename std::enable_if<std::is_same<T, VectorInt>::value>::type >
void serialize(T& vect) {
  std::cout << vect.x << std::endl;
}

U ни для чего не используется, но когда serialize передается VectorInt, теперь он выводит T из переданного аргумента, а затем выводит U, который имеет значение по умолчанию. Однако если аргумент enable_if равен false, то U не будет соответствовать ни одному типу, и создание экземпляра будет некорректным: классический SFINAE.

Этот ответ уже довольно длинный, но enable_if сама по себе достаточно глубокая тема; приведенная выше форма работает здесь, но не работает для непересекающихся наборов перегрузок. Я бы посоветовал узнать больше об ADL, выводе аргументов шаблона, SFINAE и enable_if, помимо SO (сообщения в блогах, видео Cppcon на YouTube и т. д.).

person Nir Friedman    schedule 17.03.2018
comment
Этот ответ был идеальной длины, спасибо. Ваш комментарий о том, что компилятор не имеет специальных знаний о enable_if, был очень полезен, наряду с примером нахождения T такого, что Foo<T>::type равно Bar. - person Nickolai; 17.03.2018