Почему специализирующий аргумент должен быть недействительным? - PullRequest
5 голосов
/ 19 июня 2019

Итак, еще один вопрос в этой саге . Guillaume Racicot был достаточно хорош, чтобы предоставить мне еще один обходной путь , поэтому этот код я буду основывать на этом вопросе:

struct vec
{
    double x;
    double y;
    double z;
};

namespace details
{
template <typename T>
using subscript_function = double(*)(const T&);

template <typename T>
constexpr double X(const T& param) { return param.x; }

template <typename T>
constexpr double Y(const T& param) { return param.y; }

template <typename T>
constexpr double Z(const T& param) { return param.z; }
}

template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };


int main() {
    vec foo = { 1.0, 2.0, 3.0 };

    for(const auto i : my_temp<decltype(foo)>) {
        cout << (*i)(foo) << endl;
    }
}

Кажется, проблема возникает в моей специализации, когда я возвращаю что-то другое , чем void.Например, в приведенном выше коде enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T> предотвращает специализацию, в то время как простое удаление последнего аргумента и разрешение enable_if на возврат void позволяет специализацию.

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

Live Example

Ответы [ 3 ]

7 голосов
/ 19 июня 2019

Не уверен, что понимаю то, чего не понимаешь, но ...

Если вы напишите

template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };

у вас есть первая, главная переменная шаблона с двумя шаблонами: тип и тип со значением по умолчанию (void).

Вторая переменная шаблона включается, когда std::enable_if_t равен void.

Что происходит, когда вы пишете

for(const auto i : my_temp<decltype(foo)>) 

Компилятор:

1) найти my_temp<decltype(foo)> с единственным параметром шаблона

2) найдите соответствующую my_temp переменную шаблона

3) найти только my_temp с двумя параметрами шаблона, но второй имеет значение по умолчанию, поэтому

4) решите, что my_temp<decltype(foo)> может быть только my_temp<decltype(foo), void> (или my_temp<vec, void>, если хотите)

5) увидеть, что основной my_temp соответствует

6) увидеть, что специализация my_temp не совпадает, потому что

enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>

равно T (то есть vec), поэтому может соответствовать только my_temp<vec, vec>, что отличается от my_temp<vec, void>.

7) выберите единственную доступную переменную шаблона: основную.

Если вы хотите, чтобы специализация была включена

enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>

Вы должны использовать T

// ..............................V   T! not void
template <typename T, typename = T>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

по умолчанию для второго типа шаблона в основной переменной шаблона.

Не по теме: лучше использовать std::declval в тесте std::is_floating_point_v; Я предлагаю

std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>
2 голосов
/ 19 июня 2019

Как работает специализация шаблона:

Существует основная специализация . Этот в основном определяет аргументы и значения по умолчанию.

template <typename T, typename = void>

Это шаблонная часть вашей основной специализации. Требуется один тип, затем другой тип по умолчанию void.

Это «интерфейс» вашего шаблона.

template <typename T>
[...] <T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>> [...]

здесь вторичная специализация .

В этом случае template <typename T> принципиально отличается. В основной специализации он определил интерфейс; здесь он определяет «переменные», которые используются ниже.

Тогда у нас есть часть, где мы делаем сопоставление с образцом. Это после имени шаблона (в данном случае это переменная). Переформатировано для здравомыслия:

<
  T,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(T())
      )
    >,
    T
  >
>

теперь мы можем видеть структуру. Есть два аргумента, соответствующих двум аргументам в основной специализации.

Первый - T. Теперь это соответствует имя в основной специализации, но это ничего не значит. Это похоже на вызов функции make_point(int x, int y) с переменными x,y - это может быть y,x или m,n, а make_point не волнует.

Мы представили совершенно новую переменную T в этой специализации. Затем мы связываем это с первым аргументом.

Второй аргумент сложен. Достаточно сложный, чтобы он находился в «не выводимом контексте». Как правило, аргументы специализации шаблона выводятся из аргументов, передаваемых шаблону, как определено в основной специализации; не выводимые аргументы не являются.

Если мы сделаем some_template< Foo >, то при сопоставлении типа T с Foo получим ... Foo. Довольно простой образец совпадения. Допускаются более подходящие сопоставления с образцом, например специализация, которая занимает T*; это не соответствует some_template<int>, но соответствует some_template<int*> с T=int.

Не выведенные аргументы не участвуют в этой игре. Вместо этого подключаются аргументы, которые соответствуют do , и генерируется результирующий тип. И если и только если это соответствует типу, переданному шаблону в этом слоте, специализация совпадает.

Итак, давайте рассмотрим, что происходит, мы передаем vec в качестве первого аргумента my_temp

Сначала перейдем к основной специализации

template<typename T, typename=void>
my_temp

сейчас my_temp<vec> имеет аргумент по умолчанию. Становится my_temp<vec,void>.

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

Другая специализация:

template<typename T>
[...] my_temp<
  T,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(T())
      )
    >,
    T
  >
>[...]

с [...] для вещей, которые не имеют значения.

Хорошо, первый аргумент связан с T. Ну, первый аргумент vec, так что это легко. Подставляем:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(vec())
      )
    >,
    vec
  >
>[...]

затем оцените:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    is_floating_point_v
    <
      double
    >,
    vec
  >
>[...]

и более:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    true,
    vec
  >
>[...]

и более:

template<typename T>
[...] my_temp<
  vec,
  vec
>[...]

хорошо, помните, что мы пытались сравнить с my_temp<vec,void>. Но эта специализация оценивается в my_temp<vec,vec>, и те не совпадают. Отклонено.

Удалите ,T из enable_if или сделайте его ,void (то же самое), и последняя строка приведенного выше аргумента станет my_temp<vec,void> соответствует my_temp<vec,void>, и вторичная специализация будет выбрана вместо первичной один.


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

И то, что вы обычно получаете, это кто-то, использующий его, как волшебный черный ящик, который вы копируете.

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

0 голосов
/ 19 июня 2019

С

struct vec
{
    double x;
    double y;
    double z;
};

и

template <typename T>
constexpr double X(const T& param) { return param.x; }

мы узнаем, что

is_floating_point_v<decltype(details::X(T()))

оценивается как true (если вы не собираетесьспециализируйте X для vec, чтобы не возвращать число с плавающей запятой ...).

Таким образом, мы на самом деле имеем:

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<true, T>>[]
    = { /*...*/ };

или короче:

template <typename T>
constexpr details::subscript_function<T> my_temp<T, T>[]
    = { /*...*/ };

(если он вообще существует, конечно ...).Явный выбор одного или другого:

my_temp<decltype(foo), void>
my_temp<decltype(foo), int>
my_temp<decltype(foo), double>

все соответствуют основному шаблону, но ни одна из специализаций.

my_temp<decltype(foo), decltype(foo)>

сейчас не соответствует соответствию специализации (котораясуществует из-за X(foo), возвращающего double ...).

Наконец вернемся к my_temp<decltype(foo)> - хорошо, только один заданный параметр шаблона.Какой тип второго?Параметр по умолчанию говорит вам (или лучше: компилятор), это void.И в соответствии с вышеизложенным ...

Так что, если вы хотите соответствовать специализации, либо вам нужно void в качестве типа второго параметра шаблона (как вы уже обнаружили), либо вы измените значение по умолчанию вспециализированный шаблон равен первому параметру шаблона (typename T, typename = T).

На самом деле, вы можете выбрать любой тип для значения по умолчанию и специализации, если вы выберете тот же для оба (например, дважды int, std::string, MyVeryComplexCustomClass, ...).

...