Дедукция на аргумент на шаблон и 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 Добре, съгласен съм, но като се има предвид контекста на по-ранния въпрос, изглеждаше ми ясно, че OP пита за изричното уточняване на аргументите на шаблона за конструктора. - 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