2012-03-12 7 views
8

Poniższy kod kompiluje się w GCC (użyłem ideone, który używa gcc-4.3.4), ale nie kompiluje w Visual Studio. Czy jest to standardowy kod i błąd w Visual C++ 2008 i 2010 (próbowałem go w obu) lub niestandardowy i GCC z przyjemnością go skompiluje?Czy następujący szablon specjalizacji kodu jest niestandardowy, czy błąd w VS-C++?

namespace cool 
{ 
    template <bool, typename = void> struct enable_if {}; 
    template <typename T> struct enable_if<true, T> { typedef T type; }; 

    template <typename T0, typename T1> struct is_same { enum { value = false }; }; 
    template <typename T> struct is_same<T, T> { enum { value = true }; }; 
} 

struct BasePolicy {}; 
struct BasePolicy2 {}; 
struct Faz {}; 

template <typename Policy, 
typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value || cool::is_same<BasePolicy2, Policy>::value>::type > 
struct Foo; 

template <typename Policy> 
struct Foo<Policy> { 
    Foo(); 
}; 

template <typename Policy> 
Foo<Policy>::Foo() { 
} 

int main() 
{ 
    Foo<BasePolicy2> fb; 
    // Foo<Faz> fb1; 
} 

Error 1 error C2039: '{konstruktor}': nie jest członkiem 'foo' main.cpp 25

Należy zauważyć, że problem jest out-of-line definicja konstruktora Foo. Jeśli zdefiniujemy ją w klasie, a następnie wizualna-C++ jest szczęśliwy:

template <typename Policy> 
struct Foo<Policy> { 
    Foo() {} 
}; 

Również następujący kod kompiluje zarówno (zauważ, że The || i logika po jego brak):

namespace cool 
{ 
    template <bool, typename = void> struct enable_if {}; 
    template <typename T> struct enable_if<true, T> { typedef T type; }; 

    template <typename T0, typename T1> struct is_same { enum { value = false }; }; 
    template <typename T> struct is_same<T, T> { enum { value = true }; }; 
} 

struct BasePolicy {}; 
struct BasePolicy2 {}; 
struct Faz {}; 

template <typename Policy, 
    typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value>::type > 
struct Foo; 

template <typename Policy> 
struct Foo<Policy> { 
    Foo(); 
}; 

template <typename Policy> 
Foo<Policy>::Foo() { 
} 

int main() 
{ 
    Foo<BasePolicy> fb; 
    // Foo<Faz> fb1; 
} 

kredytowych w przypadku gdy kredyt jest spowodowane, jest to nieco zmodyfikowana wersja podane mi przez Dietmar Kühl)

+0

Jakie są rzeczywiste błędy, które daje program Visual Studio? –

+0

@ James: Dodano błąd w pytaniu – Samaursa

+0

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. –

Odpowiedz

0

Och. Nie powinno to być:

template <typename Policy> 
struct Foo { 
    Foo(); 
}; 

template <typename Policy> 
Foo<Policy>::Foo() { 
} 

(Bez <Policy> w deklaracji struktury.) Nie wiem, co standardowe mówi, ale deklarowania struct szablonu z parametrem szablonu po nazwie struktura powinna być stwierdzająca częściową specjalizację ten szablon.

+0

Nie, nie powinien. W kodzie OP deklaracja, o której mówisz, jest * częściową specjalizacją * wcześniej ogłoszonego * szablonu 'Foo'. Składnia jest poprawna dla częściowej specjalizacji. To, co proponujesz, jest poprawne w przypadku "pierwotnej" deklaracji szablonu. Ale w kodzie PO nie jest to pierwotna deklaracja, ale raczej specjalizacja. – AnT

+0

Ach tak, głupi mnie. Nie udało mi się przewinąć, aby spojrzeć na cały kod. :) –

1

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.

Powiązane problemy