2016-04-16 17 views
7

Ciekawie powtarzający się wzorzec szablonu może być użyty do zaimplementowania pewnego rodzaju statycznego polimorfizmu. Na przykład:CRTP - Sprawdzanie od klasy bazowej, że wyprowadzony spełnia wymagania

#include <iostream> 

template< 
class Derived 
> 
struct Base 
{ 
    static void print () 
    { 
     std::cout << Derived::number_to_print << '\n'; 
    } 
}; 

struct Derived final : 
    Base< 
    Derived 
    > 
{ 
    static constexpr unsigned int number_to_print = 27; 
}; 

int main () 
{ 
    Derived::print(); 
} 

Zachowuje się zgodnie z oczekiwaniami i drukuje 27.

Teraz chciałbym dodać kontrole do klasy bazowej, aby stwierdzić, że klasy pochodne spełniają określone wymagania. W przykładzie podanym powyżej, takie kontrole mogą być:

#include <iostream> 
#include <type_traits> 

template< 
class Derived 
> 
struct Base 
{ 
    // --- Checks begin 
    static_assert(std::is_same< 
     decltype(Derived::number_to_print), 
     unsigned int 
     >::value, 
     "static member `number_to_print' should be of type `unsigned int'"); 
    // --- Checks end 

    static void print () 
    { 
     std::cout << Derived::number_to_print << '\n'; 
    } 
}; 

struct Derived final : 
    Base< 
    Derived 
    > 
{ 
    static constexpr unsigned int number_to_print = 27; 
}; 

int main () 
{ 
    Derived::print(); 
} 

To nie działa, ponieważ w miejscu, gdzie Base<Derived> jest tworzony, Derived został tylko do przodu oświadczył, czyli to jest niekompletne, i nic z tego jest jeszcze znany poza faktem, że jest to struktura.

Podrapałam się w głowę, ponieważ uważam, że te sprawdzenia mogą okazać się pomocne dla użytkowników klasy podstawowej, ale nie znaleźliśmy żadnego sposobu, aby to zrobić.

Czy to możliwe ?, a jeśli tak, to w jaki sposób?

+0

[Ta strona] (http://www.gotw.ca/publications/mxc++-item-4.htm) jest bardzo przydatna. – Kalrish

Odpowiedz

4

Jako noted by Kerrek SB można przesuwać asercję statyczną w ciele funkcji składowej.

A gdy zapewnisz funkcjonalność jako funkcje składowe inne niż static, zamiast aktualnej funkcji składowej static, możesz pozwolić, aby korpus funkcji potwierdzającej był ciałem konstruktora.

E.g.

#include <iostream> 
#include <type_traits> 
using namespace std; 

#define STATIC_ASSERT(e) static_assert(e, #e " // <- is required") 

template< class A, class B > 
constexpr auto is_same_() -> bool { return std::is_same<A, B>::value; } 

template< class Derived > 
struct Base 
{ 
    Base() 
    { 
     STATIC_ASSERT((
      is_same_< remove_cv_t< decltype(Derived::number_to_print) >, int >() 
      )); 
    } 

    void print() const 
    { 
     std::cout << Derived::number_to_print << '\n'; 
    } 
}; 

struct Derived final 
    : Base<Derived> 
{ 
    static constexpr int number_to_print = 27; 
}; 

auto main() 
    -> int 
{ 
    Derived::Base().print(); 
} 
3

Jako brudna sztuczka, można przenieść asercję statyczną do ciała funkcji członka. Ponieważ definicje funkcji członów efektywnie pojawiają się po po definicji klasy, typ Derived jest kompletny wewnątrz korpusu funkcji elementu.

Należy pamiętać, że funkcje składowe szablonów klas są same w sobie szablonami funkcji, a zatem są tworzone tylko w instancji, jeśli są używane (lub jeśli szablon klasy jest jawnie tworzony).

+0

Najlepiej byłoby, gdyby kontrole zostały przeprowadzone, nawet jeśli nie zastosowano żadnej metody. Jeśli nie jest to możliwe i muszą się one odbywać wewnątrz funkcji składowych, najlepsze, co potrafię wymyślić, to umieszczenie ich w prywatnej statycznej funkcji 'constexpr' wywoływanej z każdej funkcji członkowskiej, tak aby ich nie powielać. Ale nie elegancki. – Kalrish

1

Jako alternatywne podejście (pozostałe odpowiedzi są całkiem dobre rzeczywiście), można użyć prywatną metodę i polegać na sfinae:

#include <iostream> 
#include <type_traits> 

template<class Derived> 
struct Base { 
    static void print () { 
     print<Derived>(); 
    } 

private: 
    template<class D> 
    static 
    std::enable_if_t<std::is_same<std::remove_cv_t<decltype(D::N)>, unsigned int>::value> 
    print() { 
     std::cout << D::N << '\n'; 
    } 
}; 

struct Derived final: Base<Derived> { 
    static constexpr unsigned int N = 27; 
}; 

int main () { 
    Derived::print(); 
} 

ten sposób błąd pojawia się tylko wtedy, gdy rzeczywiście użyj print.

+0

Chodziło o to, aby dodać kontrole, które wcześnie wczytały błędy i wydały jasną diagnostykę, więc fakt, że kontrole odbywają się tylko wtedy, gdy tworzona jest metoda, jest przeszkodą, a nie korzyścią (problem ten wydaje się nieunikniony w statycznych metodach, tylko @Cheers "podejście z konstruktorami rozwiązuje problem). Co więcej, twoja droga nie wydaje się oferować żadnej wyraźnej diagnozy; problem zostałby pochowany w mnóstwie błędów kompilatora. – Kalrish