Вывод аргументов шаблона и SFINAE — с использованием std::enable_if

Я играю со способами фильтрации типов, передаваемых в перегруженные шаблоны функций. Я использую Visual Studio 2013.

Вопрос из трех частей:

  1. Почему мой компилятор не может вывести Blorg3?
  2. Является ли причина, по которой TFoo2(argc) вызывает ошибку компилятора, такой же, как и № 1?
  3. Есть ли способ передать параметры шаблона конструктору?

Вот пример кода:

#include <type_traits>

#define IFPTR(T,R) typename std::enable_if<std::is_pointer<T>::value, R>::type
#define IFINT(T,R) typename std::enable_if<std::is_integral<T>::value, R>::type

template <class T, IFINT(T, T)* = nullptr> int Blorg1(T n)  { return n + 1; }
template <class T, IFPTR(T, T)* = nullptr> int Blorg1(T n)  { return *n + 1; }
template <class T> IFINT(T, int) Blorg2(T n)                { return n + 1; }
template <class T> IFPTR(T, int) Blorg2(T n)                { return *n + 1; }
template <class T> int Blorg3(IFINT(T, T) n)                { return n + 1; }
template <class T> int Blorg3(IFPTR(T, T) n)                { return *n + 1; }

struct TFoo1 {
    template <class T, IFINT(T, T)* _ = nullptr> TFoo1(T n) { }
};
struct TFoo2 {
    template <class T> TFoo2(IFINT(T, T) n) { }
};

int main(int argc, char* argv[])
{
    Blorg1(argc); // intellisense not happy
    Blorg2(argc);
    Blorg3<int>(argc);  // why cant deduce?
    Blorg1(*argv); // intellisense not happy
    Blorg2(*argv); 
    Blorg3<char*>(*argv); // why cant deduce?
    (void)TFoo1(argc); // intellisense not happy
    (void)TFoo2(argc); // intellisense not happy and !!wont compile!!
    return 0;
}

person johnnycrash    schedule 19.01.2015    source источник
comment
1. Поскольку параметр шаблона появляется только внутри описателя вложенного имени, который не является выведенным контекстом (т. е. заставляет этот параметр шаблона не выводиться из аргументов для этого конкретного параметра) 2. да 3. нет   -  person Columbo    schedule 20.01.2015


Ответы (2)


Почему мой компилятор не может вывести Blorg3?

В std::enable_if<std::is_pointer<T>::value, R>::type ::type относится к вложенному имени, которое зависит от параметров шаблона T и R. Это невыведенный контекст (§14.8.2.5/5), следовательно, компилятор не выведет аргумент шаблона.

Поэтому TFoo2(argc) выдает ошибку компилятора?

Да, шаблон конструктора должен уметь выводить аргументы шаблона, а в данном случае он не может.

Есть ли синтаксис для предоставления параметров шаблона конструктору?

Нет, как я уже упоминал, вы не можете сделать это явно, они должны быть выведены или параметры шаблона должны иметь аргументы по умолчанию.

person Praetorian    schedule 19.01.2015
comment
@MichaelGazonda Я не понимаю, что ты имеешь в виду - person Praetorian; 20.01.2015
comment
Есть ли синтаксис для предоставления параметров шаблона конструктору? - Да, дедукция сделает это. Не было указано, должна ли передача быть явной или нет... просто это нужно было сделать. - person Michael Gazonda; 20.01.2015
comment
@MichaelGazonda Хорошо, согласен, но, учитывая контекст предыдущего вопроса, мне казалось очевидным, что ОП спрашивал о явном указании аргументов шаблона для конструктора. - person Praetorian; 20.01.2015
comment
Как правильно сказать, что я имел в виду? Я думал, что дедукция — это то, что должен делать компилятор, когда вы не указали явно с помощью ‹›. - person johnnycrash; 20.01.2015
comment
@johnnycrash Это действительно то, что означает вычет, в данном случае он запрещен. Я думаю, ваша формулировка достаточно ясна, но если вы хотите ее изменить, вы можете сказать предоставить параметры шаблона явно конструктору - person Praetorian; 20.01.2015

Ответ на 1/2 о причине, по которой SFINAE не работает:

SFINAE и вывод параметров шаблона в этом контексте плохо сочетаются друг с другом.

Или они действуют до тех пор, пока вы знаете, в каком порядке происходят события.

Вывод должен гарантировать работу, чтобы его можно было рассматривать как возможную функцию, которую можно вызвать в этом случае.

Вот способ взглянуть на это менее технически:

  1. Компилятор ищет возможные сигнатуры функций, соответствующие тому, что вы пытаетесь вызвать. [см. разрешение перегрузки]

  2. Если он находит параметр шаблона, он проверяет, подходит ли он для вывода.

И именно поэтому вы сталкиваетесь с проблемами. Порядок, в котором происходят эти два события, является причиной того, что SFINAE работает с Blorg1, Blorg2 и TFoo1, но не с Blorg3 или TFoo2.

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

template <class T> int Blorg3(IFINT(T, T) n)                { return n + 1; }
template <class T> int Blorg3(IFPTR(T, T) n)                { return *n + 1; }
Blorg3<char*>(*argv); // why cant deduce?

Чтобы решить SFINAE в Blorg3 здесь, необходимо знать T. Однако T неизвестно, пока не будет устранена ошибка SFINAE.

То же самое касается и того, почему TFoo2 не работает.

Часть 3 - о шаблонах и конструкторах

Да, вы можете передавать параметры шаблона в конструкторы, но только если вы делаете это с помощью вывода, как это было сделано с TFoo1.

Вы не можете явно передавать параметры шаблона конструктору.

person Michael Gazonda    schedule 19.01.2015
comment
Спасибо за объяснение! Поскольку разрешение перегрузки игнорирует возвращаемый тип, оба Blorg1 будут соответствовать проверке подписи. Параметр шаблона легко выводится, так как blorg1 и 2 blorg1 генерируются с учетом типа. Одна из функций не проходит последующую проверку синтаксиса. blorg2 работает так же. Когда достигается невыводимая версия blorg3, функция не генерируется. - person johnnycrash; 20.01.2015