Yalnızca belirli türleri kabul eden C ++ şablonları


159

Java'da, yalnızca seçtiğiniz sınıfı genişleten türleri kabul eden genel sınıf tanımlayabilirsiniz, örneğin:

public class ObservableList<T extends List> {
  ...
}

Bu, "genişletir" anahtar kelimesi kullanılarak yapılır.

C ++ bu anahtar kelime için basit bir eşdeğer var mı?


oldukça eski bir soru zaten ... Burada (cevaplardan da) eksik olan şey, Java jeneriklerinin gerçekten C ++ şablonlarına eşdeğer olmasıdır. Benzerlikler var, ama imho bir doğrudan bir java çözüm C ++ 'a sadece farklı tür sorunlar için yapılmış olduğunu anlamak için çevirmek için dikkatli olmalıdır;)
idclev 463035818

Yanıtlar:


104

Ben Boost kullanmanızı öneririz statik assert ile uyum içinde özelliğini is_base_ofBoost Tipi Özellikleri kütüphaneden:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

Diğer bazı, daha basit durumlarda, yalnızca bir genel şablonu bildirebilir, ancak yalnızca geçerli türler için tanımlayabilir (açık veya kısmen uzmanlaştırabilirsiniz):

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Küçük değişiklik 2013/06/12: bir ilan-but-l-tanımlanmamış şablonunu kullanarak neden olur bağlayıcı ., Derleyici değil, hata mesajları]


Statik ekler de güzel. :)
macbirdie

5
@John: Uzmanlığın sadece myBaseTypetam olarak eşleşeceğinden korkuyorum . Boost'u kapatmadan önce, çoğunun yalnızca başlık şablon kodu olduğunu bilmelisiniz - bu nedenle kullanmadığınız şeyler için çalışma zamanında bellek veya zaman maliyeti yoktur. Ayrıca burada ( BOOST_STATIC_ASSERT()ve is_base_of<>) kullanacağınız belirli şeyler yalnızca bildirimler kullanılarak uygulanabilir (yani işlevlerin veya değişkenlerin gerçek tanımları yok ), böylece yer veya zaman gerektirmezler.
j_random_hacker

50
C ++ 11 geldi. Şimdi kullanabiliriz static_assert(std::is_base_of<List, T>::value, "T must extend list").
Siyuan Ren

2
BTW, çift parantezin gerekli olmasının nedeni, BOOST_STATIC_ASSERT'in bir makro olması ve ekstra parantezin önişlemcinin is_base_of işlev argümanlarındaki virgülün 2. makro argümanı olarak yorumlanmasını engellemesidir.
jfritz42

1
@Andreyua: Neyin eksik olduğunu gerçekten anlamıyorum. Bir değişkeni bildirmeyi deneyebilir my_template<int> x;veya my_template<float**> y;derleyicinin bunlara izin verdiğini doğrulayabilir ve daha sonra bir değişkeni bildirip my_template<char> z;vermediğini doğrulayabilirsiniz.
j_random_hacker

134

Buradaki diğer yanıtların belirttiği gibi, bu genellikle C ++ 'da garanti edilmez. C ++ 'da, "bu sınıftan miras" dışındaki diğer kısıtlamalara dayalı genel türleri tanımlama eğilimindeyiz. Bunu gerçekten yapmak istiyorsanız, C ++ 11'de yapmak oldukça kolaydır ve <type_traits>:

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

Bu, insanların C ++ 'da beklediği birçok kavramı kırıyor. Kendi özelliklerinizi tanımlamak gibi hileler kullanmak daha iyidir. Örneğin observable_list, typedef'leri olan const_iteratorve döndüren bir beginve endüye işlevi olan herhangi bir kapsayıcı türünü kabul etmek istiyor olabilir const_iterator. Bunu, kendisinden devralan sınıflarla sınırlarsanız list, kendisinden devralmayan, listancak bu üye işlevlerini ve typedefs'i sağlayan kendi türüne sahip bir kullanıcı ,observable_list .

Bu sorunun iki çözümü var, bunlardan biri hiçbir şeyi kısıtlamamak ve ördek tipine güvenmemek. Bu çözümün büyük bir yararı, kullanıcıların grok yapması zor olabilecek büyük miktarda hata içermesidir. Başka bir çözüm, arayüz gereksinimlerini karşılamak için sağlanan türü kısıtlamak için özellikler tanımlamaktır. Bu çözümün en büyük yanı, can sıkıcı olarak görülebilen ekstra yazı içermesidir. Ancak, olumlu tarafı kendi hata mesajlarınızı la yazabilmenizdir static_assert.

Tamlık için yukarıdaki örneğe çözüm verilmiştir:

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

Yukarıdaki örnekte, C ++ 11'in özelliklerini sergileyen birçok kavram bulunmaktadır. Meraklı için bazı arama terimleri varyasyon şablonları, SFINAE, SFINAE ifadesi ve tür özellikleridir.


2
C ++ şablonlarının bugüne kadar ördek yazmayı kullandığını hiç fark etmedim. Biraz tuhaf!
Andy

2
Kapsamlı politika kısıtlamaları göz önüne alındığında C ++ tanıtıldı C , emin niçin template<class T:list>böyle bir kusurlu kavramı. Bahşiş için teşekkürler.
bvj

61

Henüz kimsenin bahsetmediği basit çözüm, sorunu görmezden gelmektir. Bir intvektör veya liste gibi bir kapsayıcı sınıfı bekleyen bir işlev şablonunda bir şablon türü olarak kullanmayı denerseniz , o zaman bir derleme hatası alırsınız. Kaba ve basit, ama sorunu çözüyor. Derleyici belirttiğiniz türü kullanmayı dener ve başarısız olursa bir derleme hatası oluşturur.

Bununla ilgili tek sorun, aldığınız hata mesajlarının okunması zor olacaktır. Yine de bunu yapmanın çok yaygın bir yoludur. Standart kitaplık, şablon türünden belirli davranışlar bekleyen işlev veya sınıf şablonlarıyla doludur ve kullanılan türlerin geçerli olup olmadığını kontrol etmek için hiçbir şey yapmaz.

Daha güzel hata mesajları istiyorsanız (veya derleyici hatası üretmeyen, ancak yine de mantıklı olmayan vakaları yakalamak istiyorsanız), ne kadar karmaşık yapmak istediğinize bağlı olarak, Boost'un statik onayını veya Boost concept_check kütüphanesi.

Güncel bir derleyici ile static_assertbunun yerine kullanılabilecek bir built_in var .


7
Evet, her zaman şablonların C ++ yazarak ördeklere en yakın şey olduğunu düşündüm. Bir şablon için gerekli tüm öğelere sahipse, bir şablonda kullanılabilir.

@John: Özür dilerim, bunun için kafa ya da kuyruk yapamam. Hangi tür Tve bu kod nereden çağrılıyor? Bazı bağlamlar olmadan, bu kod snippet'ini anlama şansım yok. Ama söylediğim doğru. Üye işlevi toString()olmayan bir türü çağırmayı denerseniz toString, derleme hatası alırsınız.
jalf

@John: Bir dahaki sefere, sorun kodunuzdayken belki de biraz daha az tetikleyici-mutlu downvoting insanlar olmalısınız
jalf

@jalf, tamam. +1. Bu sadece en iyisi olmaya çalışarak harika bir cevaptı. Yanlış okuduğum için üzgünüm. Yazı tipini sınıflar için bir parametre olarak kullanmaktan bahsettiğimizi sanıyordum, ki eski üyelerdi, ancak derleyicinin bayrağını çağırması gerekiyor.
John

13

Biz kullanabilir std::is_base_ofve std::enable_if:
( static_assertyukarıdaki sınıflar özel uygulamaya veya kadar kullanılabilir çıkarılabilir boost biz referans, eğer type_traits)

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}

13

Bildiğim kadarıyla bu şu anda C ++ mümkün değildir. Ancak, yeni C ++ 0x standardında aradığınız işlevselliği sağlayan "kavramlar" adlı bir özellik eklemeyi planlıyoruz. C ++ Kavramları hakkındaki bu Wikipedia makalesinde daha ayrıntılı olarak açıklanacaktır.

Bunun acil sorununuzu çözmediğini biliyorum, ancak yeni standarttan özellikler eklemeye zaten başlayan bazı C ++ derleyicileri var, bu yüzden zaten kavramlar özelliğini uygulayan bir derleyici bulmak mümkün olabilir.


4
Kavramlar maalesef standarttan çıkarıldı.
macbirdie

4
C ++ 20 için kısıtlamalar ve kavramlar benimsenmelidir.
Petr Javorik

static_assertDiğer cevapların da gösterdiği gibi, kavramlar, SFINAE ve kavramlar olmadan bile mümkündür . Java veya C # veya Haskell'den (...) gelen biri için kalan sorun, C ++ 20 derleyicisinin Java ve C #'ın yaptığı gerekli kavramlara karşı tanım denetimi yapmamasıdır .
user7610

10

Sanırım önceki tüm cevaplar ağaçlar için orman görüşünü kaybetti.

Java jenerikleri şablonlarla aynı değildir ; Kullandıkları tip silinti bir olduğunu, dinamik teknik ziyade, derleme zamanı polimorfizmi olduğunu statik tekniği . Bu iki farklı taktiğin neden iyi gelmediği açık olmalıdır.

Bir çalışma zamanını simüle etmek için bir derleme zamanı yapısı kullanmaya çalışmak yerine, extendsaslında ne işe yaradığına bakalım : Stack Overflow ve Wikipedia'ya göre , extends alt sınıfı belirtmek için kullanılır.

C ++ alt sınıflamayı da destekler.

Ayrıca, genel olarak form silme özelliğini kullanan ve bir tür denetimi gerçekleştirmek için genişletilen bir kapsayıcı sınıfı da gösterirsiniz. C ++ 'da, silme makinelerini kendiniz yapmanız gerekir, bu basittir: üst sınıfa bir işaretçi yapın.

Bütün bir sınıf yapmaktan ziyade, kullanımı daha kolay hale getirmek için bir typedef'e saralım:

typedef std::list<superclass*> subclasses_of_superclass_only_list;

Örneğin:

class Shape { };
class Triangle : public Shape { };

typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;

shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes

Şimdi, Liste bir çeşit koleksiyonu temsil eden bir arayüz gibi görünüyor. C ++ 'da bir arayüz sadece soyut bir sınıf, yani saf sanal yöntemlerden başka bir şey olmayan bir sınıf olacaktır. Bu yöntemi kullanarak, Java örneğinizi herhangi bir Kavram veya şablon uzmanlığı olmadan C ++ 'da kolayca uygulayabilirsiniz. Ayrıca, sanal tablo aramaları nedeniyle Java stili jenerikleri kadar yavaş çalışacaktır, ancak bu genellikle kabul edilebilir bir kayıp olabilir.


3
Ben "açık olmalı" veya "herkes bilir" gibi ifadeler kullanan cevapların hayranı değilim ve daha sonra açık veya evrensel olarak bilinenleri açıklamaya devam ediyorum. Açık, bağlam, deneyim ve deneyim bağlamı ile ilgilidir. Bu tür ifadeler doğal olarak kabadır.
3Dave

2
@DavidLively Görgü kuralları için bu cevabı eleştirmek için yaklaşık iki yıl çok geç, ama aynı zamanda bu özel örnekte de size katılmıyorum; Neden iki tekniğin neden açık olduğunu belirtmeden önce bir araya gelmediklerini açıkladım , sonra değil. Bağlam sağladım ve daha sonra bu bağlamdan çıkan sonucun açık olduğunu söyledim. Bu, kalıbınıza tam olarak uymuyor.
Alice

Bu cevabın yazarı, ağır bir kaldırma yaptıktan sonra bir şeyin belirgin olduğunu söyledi. Yazarın çözümün açık olduğunu söylemeyi amaçladığını sanmıyorum.
Luke

10

Yalnızca Liste listesinden türetilen T türlerini kabul eden bir eşdeğer

template<typename T, 
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};

8

Yönetici özeti: Bunu yapma.

j_random_hacker'ın cevabı size bunun nasıl yapılacağını anlatır . Ancak, bunu yapmamanız gerektiğini de belirtmek isterim . Şablonların asıl amacı, herhangi bir uyumlu türü kabul edebilmeleridir ve Java stili tür kısıtlamaları bunu kırmaktadır.

Java'nın tür kısıtlamaları bir özellik değil bir hatadır. Java jenerikler üzerinde silme işlemi yaptığı için oradalar, bu nedenle Java yalnızca tür parametrelerinin değerine dayalı olarak yöntemlerin nasıl çağrılacağını anlayamaz.

C ++ ise böyle bir kısıtlamaya sahip değildir. Şablon parametre türleri, birlikte kullanıldıkları işlemlerle uyumlu herhangi bir tür olabilir. Ortak bir temel sınıf olmak zorunda değildir. Bu, Python'un "Duck Typing" e benzer, ancak derleme zamanında yapılır.

Şablonların gücünü gösteren basit bir örnek:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

Bu toplama işlevi, doğru işlemleri destekleyen her türden bir vektörü toplayabilir. Hem int / long / float / double hem de + = operatörünü aşırı yükleyen kullanıcı tanımlı sayısal türlerle çalışır. Heck, bu işlevi dizeleri birleştirmek için bile kullanabilirsiniz, çünkü + = destekliyorlar.

İlkellerin boksu / kutudan çıkarılması gerekmez.

Ayrıca T () kullanarak yeni T örnekleri oluşturduğunu unutmayın. Bu, örtülü arabirimler kullanan C ++ 'da önemsizdir, ancak Java'da tür kısıtlamaları ile gerçekten mümkün değildir.

C ++ şablonlarının açık tür kısıtlamaları olmasa da, yine de tür güvenlidir ve doğru işlemleri desteklemeyen kodlarla derlenmez.


2
Şablonları asla uzmanlaştırmayı önermiyorsanız, bunun neden dilde olduğunu da açıklayabilir misiniz?

1
Anladım, ancak şablon argümanınızın belirli bir türden türetilmesi gerekiyorsa, static_assert'ten mesajı normal derleyici hatası kusmuktan daha kolay yorumlamak daha iyidir.
jhoffman0x

1
Evet, C ++ burada daha etkileyici, ancak bu genellikle iyi bir şey olsa da (çünkü daha azıyla daha fazla ifade edebiliyoruz), bazen bir sistemi tam olarak anladığımızdan emin olmak için kendimize verdiğimiz gücü kasten sınırlamak istiyoruz .
j_random_hacker

@ Kaburga tipi uzmanlığı, sadece belirli tipler için yapılabilecek bir şeyden yararlanmak istediğinizde kullanışlıdır. örneğin bir boolean normalde 8 bit / boolean tutabilse de, her bir bayt bir bayttır; bir şablon toplama sınıfı (ve std :: map durumunda), boole için uzmanlaşabilir, böylece belleği korumak için verileri daha sıkı bir şekilde paketleyebilir.
thecoshman

Ayrıca, açıklığa kavuşturmak için, bu cevap "asla şablonları özelleştirme" demek değildir, bir şablonla hangi türlerin kullanılabileceğini sınırlamak için bu özelliği kullanmayın.
thecoshman

6

Bu düz C ++ 'da mümkün değildir, ancak Konsept Kontrolü aracılığıyla şablon parametrelerini derleme zamanında doğrulayabilirsiniz, örneğin Boost BCCL'sini kullanarak .

C ++ 20'den itibaren kavramlar dilin resmi bir özelliği haline geliyor .


2
Eh, o ise mümkün ama konsept denetimi yine de iyi bir fikirdir. :)
j_random_hacker

Aslında "düz" C ++ 'da mümkün olmadığı anlamına geliyordu. ;)
macbirdie

5
class Base
{
    struct FooSecurity{};
};

template<class Type>
class Foo
{
    typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};

Türetilmiş sınıfların FooSecurity yapısını miras aldığından ve derleyicinin tüm doğru yerlerde sinirleneceğinden emin olun.


@Zehelvion Type::FooSecurityşablon sınıfında kullanılır. Şablon bağımsız değişkeninde iletilen sınıf FooSecuritybunu kullanmadıysa, bunu kullanmaya çalışmak hataya neden olur. Şablon argümanında iletilen sınıfın türetildiği FooSecurity olmadığından emin olabilirsiniz Base.
GingerPlusPlus

2

C ++ 20 konsept kullanımı

https://en.cppreference.com/w/cpp/language/constraints cppreference, miras kullanım durumunu açık bir konsept örneği olarak veriyor:

template <class T, class U>
concept Derived = std::is_base_of<U, T>::value;
 
template<Derived<Base> T>
void f(T);  // T is constrained by Derived<T, Base>

Birden fazla baz için sözdizimi olacağını tahmin ediyorum:

template <class T, class U, class V>
concept Derived = std::is_base_of<U, T>::value || std::is_base_of<V, T>::value;
 
template<Derived<Base1, Base2> T>
void f(T);

GCC 10 bunu uygulamış gibi görünüyor: https://gcc.gnu.org/gcc-10/changes.html ve Ubuntu 20.04'te PPA olarak alabilirsiniz . https://godbolt.org/ Yerel GCC 10.1'im concepthenüz tanımadı , bu yüzden neler olup bittiğinden emin değilim.


1

C ++ bu anahtar kelime için basit bir eşdeğer var mı?

Hayır.

Neyi başarmaya çalıştığınıza bağlı olarak, yeterli (veya daha iyi) yedekler olabilir.

Ben bazı STL kodu (linux üzerinde, ben SGI'nın uygulanmasından türetmek düşünüyorum) baktı. “Kavram iddiaları” vardır; örneğin, anlayan bir türe gereksinim duyarsanız *xve ++xkavram iddiası bu kodu bir hiçbir şey yapma işlevinde (veya benzer bir şeyde) içerir. Bazı ek yükler gerektirir, bu yüzden tanımı bağlı bir makroya koymak akıllıca olabilir #ifdef debug.

Alt sınıf ilişkisi gerçekten bilmek istediğiniz şeyse, yapıcıda T instanceof list(C ++ 'da "yazıldığından" farklı olduğunu iddia edebilirsiniz ). Bu şekilde, derleyiciden çıkış yolunu sizin için kontrol edemediğinizi test edebilirsiniz.


1

Bu tür kontroller için anahtar kelime yoktur, ancak en azından düzenli bir şekilde başarısız olacak bazı kodlar koyabilirsiniz:

(1) Bir işlev şablonunun yalnızca belirli bir temel sınıf X'in parametrelerini kabul etmesini istiyorsanız, bunu işlevinizdeki bir X referansına atayın. (2) İşlevleri kabul etmek istiyorsanız, ancak ilkel veya tersi değilse veya sınıfları başka şekillerde filtrelemek istiyorsanız, işlevinizde yalnızca kabul etmek istediğiniz sınıflar için tanımlanan (boş) bir şablon yardımcı işlevini çağırın.

Bir sınıfın üye işlevlerinde (1) ve (2) öğelerini de bu tür denetimleri tüm sınıfta zorlamak için kullanabilirsiniz.

Acınızı hafifletmek için muhtemelen akıllı bir Makroya koyabilirsiniz. :)


-2

Şablonunuzu aşağıdaki gibi bir şey okuyarak oluşturabilirsiniz:

template<typename T>
class ObservableList {
  std::list<T> contained_data;
};

Ancak bu, kısıtlamanın örtük olmasını sağlar, ayrıca yalnızca liste gibi görünen hiçbir şeyi sağlayamazsınız. Kullanılan kap türlerini kısıtlamanın başka yolları da vardır, örneğin tüm kaplarda bulunmayan belirli yineleyici türlerini kullanarak, ancak yine bu açık bir kısıtlamadan daha örtüktür.

Bildiğim kadarıyla, Java ifadesini tam olarak yansıtacak bir yapı mevcut standartta mevcut değil.

Şablonunuzun içindeki belirli typedef'leri kullanarak yazdığınız bir şablonun içinde kullanabileceğiniz türleri kısıtlamanın yolları vardır. Bu, belirli bir typedef içermeyen bir tür için şablon uzmanlığının derlenmesinin başarısız olmasını sağlar, böylece belirli türleri seçici olarak destekleyebilir / desteklemeyebilirsiniz.

C ++ 11'de, kavramların tanıtımı bunu kolaylaştırmalıdır, ancak tam olarak ne istediğinizi yapacağını sanmıyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.