Program Visual C++ 2008/2010 jest wadliwy. Ale można go obejść - na więcej niż jeden sposób.
Rozważmy typ Foo<BasePolicy2> fb
.
Ta deklaracja domyślnie przyjmuje drugi parametr szablonu szablonu Foo <> jako pierwszy zadeklarowany. Tak wyraźnie jego typ to:
/*1*/ Foo<BasePolicy2,cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type
>
Jeśli jesteś już przekonany, że /*1*/
sprowadza się do:
/*2*/ Foo<BasePolicy2,void>
następnie można nas spotkać ponownie w Rendezvous poniżej.
Cóż, możemy zobaczyć, że typ:
/*3/ cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>
postanawia:
/*4/ cool::enable_if<some_boolean_consant>
Następnie niech sprawdzić jak template enable_if<>
jest zdefiniowana.W namespace cool
mamy:
/*5/ template <bool, typename = void> struct enable_if {};
/*6/ template <typename T> struct enable_if<true, T> { typedef T type; };
tak /*4*/
z kolei jest zalegających 2nd parametr szablonu template enable_if<>
oraz rodzaj tego domyślnie jest void
.
Ok, następnie /*6*/
specjalizuje template enable_if<>
w odniesieniu do jego drugiego parametru szablonu, gdy jego pierwszy parametr bool ma wartość true
i mówi w tym przypadku enable_if<>
wywozi typedef type
który ma typ 2. szablonu parametr. Jeśli pierwszym parametrem bool jest false
, to ten typedef po prostu nie będzie istnieć, a nasz kompilator będzie barfować.
Dobrze wiemy, że jeśli /*4*/
będzie skompilować w ogóle, to some_boolean_consant == true
i eksportowane rodzaj type
jest to, że defaulted 2. parametr szablon enable_if<>
. Który jest void
.
Now've wywnioskować typ:
/*7*/ cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type
To jest void
. Tak więc /*1*/
sprowadza się do /*2*/
, a to jest domyślny typ Foo<BasePolicy2>
.
Który jest tak jak powinno być, ale jeśli są wątpliwe z tego wniosku, a potem po prostu dodać do programu w zakresie globalnym i skompilować:
typedef cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type WhatType;
WhatType what_am_i;
Kompilator powie:
'what_am_i' : illegal use of type 'void'
lub słów w tym celu.
Rendezvous
Wiedza że /*1/
= /*2/
daje nam jakąś dźwignię na Visual C++ błąd kompilacji, że pytanie jest o:
Error 1 error C2039: '{ctor}' : is not a member of 'Foo' main.cpp 25
Błąd narzeka, że konstruktor powodując jej nie jest w rzeczywistości konstruktorem deklarowanym przez typ, do którego musi należeć, tj. że Foo<Policy>::Foo()
nie jest konstruktorem z Foo<Policy>
.
Teraz definicja Foo<Policy>
domyślnie przyjmuje drugi parametr szablonu początkowej deklaracji, która, jak wiemy, musi być void
. Pytanie o numer przedstawia się następująco: Czy w rzeczywistości kompilator honoruje ten domyślny drugi parametr szablonu? - tj. czy rozpoznaje, że definicja szablonu z Foo<Policy>
jest szablonową definicją Foo<Policy,void>
?
Odpowiedź nr Bo jeśli po prostu zmienić definicję szablonu i jego konstruktora jawnie określić domyślny 2nd parametr:
template <typename Policy>
struct Foo<Policy,void> {
Foo();
};
template <typename Policy>
Foo<Policy,void>::Foo() {
}
następnie program kompiluje czyste z Visual C++.
Czy Visual C++ ewentualnie trzymanie się na własną rękę - dyskusyjna - przekonania o standard C++, podnosząc ten błąd? A może jest po prostu zepsuty? Po prostu jest zepsuty. Bo jeśli staramy się go na to prostsze, ale w zasadzie ten sam program nie ma problemu:
/* Simple Case */
template<typename X, typename Y = void>
struct A;
template<typename X>
struct A<X> {
A();
};
template<typename X>
A<X>::A(){};
int main()
{
A<int> aint;
return 0;
}
To pokazuje, że to, że łyk szablonu metaprogramowanie /*3*/
który jest przyczyną niestrawności, i jak wskazuje pytający, czy że kęs jest eksperymentalnie uproszczone poprzez usunięcie operację ||
, wszystko jest dobrze (poza tym, że nasz cool::
logika jest łamane, oczywiście).
obejść?
Widzieliśmy już jeden. Wystarczy dokonać parametr void
szablon wyraźny w definicjach Foo<Policy>
i Foo<Folicy>::Foo()
. Możesz już wyjść, jeśli chcesz tylko to wiedzieć.
Ale ten swędzi. Stosujemy poprawkę na poziomie /* Simple Case */
, gdy wiemy, że błąd nie jest generalny na tym poziomie. Jest on niedostępny w metaprogramowaniu szablonów kompilatora, więc obejście problemu, które nie świeci, powinno być ograniczone do namespace cool
.
Oto zasada ostrożności o szablon metaprogramowanie (TMP) szablonów pomocniczych, które mam nadzieję, że postęp kompilatorów wkrótce pozwól mi zapomnieć: Nie pozwól nędznicy instancji w locie. Takie szablony istnieją tylko w wersji , aby ułatwić logikę kompilacji. Dopóki kompilator może wykonywać tę logikę tylko przez rekursywne definiowanie typów, prawdopodobnie pozostanie w swojej strefie komfortu (co najmniej tak długo, jak długo jego wartość jest pusta). Jeśli jest zobowiązany do utworzenia pośrednich typów w celu rozwinięcia logiki kompilacji, wówczas najdziwniejsze są jej peccadille.
Jeśli kodzie klas pomocników TMP w taki sposób, że compiletime logika może być dokonane wyłącznie przez przyjmując wartości statycznych lub wyliczonych stałych, które narażają, a następnie zmusić kompilator do wystąpienia przez cały czas, ponieważ tylko w ten sposób statyczne lub wyliczone stałe uzyskują wartości.
Przypadek w tej klasie, klasa /*3*/
z jego tajemniczo toksyczną operacją ||
. Klasy pomocnicze TMP z namespace cool
wykonują swoją meta-logikę dla stałych logicznych - niepotrzebnie.
Ostrożne podejście do klas pomocniczych TMP polega na zdefiniowaniu ich tak, aby wszystkie operacje logiczne mogły być symulowane, bez tworzenia instancji, przez rekursywne definicje typów, z eksportowanymi stałymi - aby ostatecznie ich nie potrzebować - tylko wtedy, gdy wszystkie compiletime logika została pomyślnie wyładowana.Ostrożne re-write zawartości namespace cool
może wyglądać mniej więcej tak:
namespace cool
{
template<bool val>
struct truth_type
{
static const bool value = false;
};
template<>
struct truth_type<true>
{
static const bool value = true;
};
typedef truth_type<true> true_type;
typedef truth_type<false> false_type;
template<class lhs,class rhs>
struct or_type
{
typedef false_type type;
};
template<class lhs>
struct or_type<lhs,true_type>
{
typedef true_type type;
};
template<class rhs>
struct or_type<true_type,rhs>
{
typedef true_type type;
};
template <typename T, typename = void> struct enable_if {};
template <typename T> struct enable_if<true_type, T> { typedef T type; };
template <typename T0, typename T1> struct is_same {
typedef false_type type;
};
template <typename T> struct is_same<T, T> {
typedef true_type type;
};
}
i odpowiednią deklarację szablonu Foo <> wyglądałby następująco:
template <typename Policy,
typename = typename cool::enable_if<
typename cool::or_type<
typename cool::is_same<BasePolicy, Policy>::type,
typename cool::is_same<BasePolicy2, Policy>::type
>::type
>::type>
struct Foo;
ten sposób żaden z naszych cool::
rzeczy są zawsze tworzone, dopóki cały shebang nie zostanie utworzony : zajmujemy się wyłącznie typami dla wszystkich meta-logik. Jeśli te zmiany zostaną wprowadzone do programu, wówczas nie będzie konieczne stosowanie swędzącego rozwiązania. I GCC jest z tego zadowolony.
Jakie są rzeczywiste błędy, które daje program Visual Studio? –
@ James: Dodano błąd w pytaniu – Samaursa
Może powinieneś zamieścić link do oryginalnego pytania, na które Dietmar zasugerował takie podejście. Dodanie komentarza do jego odpowiedzi z linkiem do tego pytania może zwrócić jego uwagę. Jestem pewien, że on * zna * odpowiedź na to pytanie. –