2015-01-13 14 views
8
sentezleme şablonları cRTP, sentezleme hiyerarşisinin en üstünde sınıfı kullanılarak uygulanan

bazı operasyonlarını uygulamak için taban ile türetilmiş downcasting kullanır. tınlamak-3.5 için (-std=c++1y) göre, bu mahzun constexpr işlevlerinde yasadışı olmalıdır:constexpr ve cRTP: derleyici anlaşmazlık

test.cpp:42:16: error: static_assert expression is not an integral constant expression 
     static_assert(e() == 0, ""); 
         ^~~~~~~~ 
test.cpp:11:26: note: cannot cast object of dynamic type 'const base<derived>' to type 'const derived' 
     const noexcept { return static_cast<const Derived&>(*this)(); } 

GCC mutlu compiles the code. Peki kim haklı? Clang haklıysa, constexpr işlevinde hangi C++ 14 kısıtlaması bu yayınlamayı yasa dışı yapar?

template <class Derived> 
class base 
{ 
public: 
    constexpr auto operator()() 
    const noexcept { return static_cast<const Derived&>(*this)(); } 
}; 

class derived : public base<derived> 
{ 
public: 
    constexpr auto operator()() 
    const noexcept { return 0; } 
}; 

template <class A, class B> 
class expr : public base<expr<A, B>> 
{ 
    const A m_a; 
    const B m_b; 
public: 
    constexpr explicit expr(const A a, const B b) 
    noexcept : m_a(a), m_b(b) {} 

    constexpr auto operator()() 
    const noexcept { return m_a() + m_b(); } 
}; 

template <class D1, class D2> 
constexpr auto foo(const base<D1>& d1, const base<D2>& d2) 
noexcept { return expr<base<D1>, base<D2>>{d1, d2}; } 

int main() 
{ 
    constexpr auto d = derived{}; 
    constexpr auto e = foo(d, d); 
    static_assert(e() == 0, ""); 
} 

cevap

9

. Bununla birlikte, e üyeleri, derived'un kendisinin değil, base<derived> tipindedir. hat

const noexcept { return m_a() + m_b(); } 

m_a tip base<derived> sahiptir ve base<derived>::operator() denir - tip base<derived> bir çok türetilmiş nesne ile .
Bu nedenle, cast, *this'u, aslında başvurmadığı nesne türüne yapılan bir başvuruya göndermeye çalışır;

CV1 B,” B bir sınıf türü olan tipte bir lvalue, tipine dökülebilir

“referans: [expr.static.cast]/2 tarif gibi bu işlem tanımsız davranış olurdu cv2D, "D türetilmiş (Madde 10) B [..] 'dan türetilmiştir. tip nesne “CV1 B” aslında tip D bir nesnenin bir subobject ise, sonuç tip D kapatılmasının nesnesine ifade eder. Aksi halde, davranış tanımsızdır.

ve daha sonra, [expr.const]/2 geçerlidir:

bir koşullu ekspresyonuee değerlendirilmesi sürece bir çekirdek sabit ifadesidir, özet kurallarına aşağıdaki

(2: aşağıdaki ifadeler birini değerlendirecek makinesi (1.9).5) - bir operasyon şu şekilde foo yeniden, tanımsız davranış yerine

olurdu:

template <class D1, class D2> 
constexpr auto foo(const D1& d1, const D2& d2) 
noexcept { return expr<D1, D2>{d1, d2}; } 

Ve kod works fine.

5

O Clang bu durumda haklı geliyor bana:

İşte MWE bu. e tipi const expr<base<derived>, base<derived>>, yani m_a ve m_b tip base<derived> yerine derived daha var. Başka bir deyişle, m_a ve m_b'a kopyaladığınızda slicedd var. Geçerli bir static_cast, this puan (veya bunun bir alt-sınıfına) tip Derived olmalıdır için bu en türetilmiş nesne yapmak için base içinde operator() için

+0

Askerin standart referanslar aradığını düşünüyorum. – Columbo

+0

@Columbo Bazı bulmaya çalışacağım. Ancak genel bir kural olarak, çalışma zamanında tanımlanmamış davranışların çoğu (tümü?), “Constexpr” değerlendirmelerinde hatalardır. –

+0

Kodun iyi olduğunu düşündüm, daha yakından bakmama izin ver – Columbo

2

Orijinal kodunuzun iyi bir şekilde çalışması. UB kaldırılır, ve o güzel uzanır:

namespace library{ 
    template <class Derived> 
    class base 
    { 
    public: 
    constexpr Derived const& self() const noexcept { return static_cast<const Derived&>(*this); } 

    constexpr auto operator()() 
    const noexcept { return self()(); } 
    }; 

    template <class A, class B> 
    class expr : public base<expr<A, B>> 
    { 
    const A m_a; 
    const B m_b; 
    public: 
    constexpr explicit expr(const A a, const B b) 
    noexcept : m_a(a), m_b(b) {} 

    constexpr auto operator()() 
    const noexcept { return m_a() + m_b(); } 
    }; 

    template <class D1, class D2> 
    constexpr auto foo(const base<D1>& d1, const base<D2>& d2) 
    noexcept { return expr<D1, D2>{d1.self(), d2.self()}; } 
} 

namespace client { 
    class derived : public library::base<derived> { 
    public: 
    constexpr auto operator()() 
    const noexcept { return 0; } 
    }; 
} 


int main() 
{ 
    constexpr auto d = client::derived{}; 
    constexpr auto e = foo(d, d); 
    static_assert(e() == 0, ""); 
} 

Temelde her base<X> bir X olmalıdır. Bu nedenle, depoladığınızda, base<X> değil, X olarak saklarsınız. X ürününe base<X>::self() aracılığıyla constexpr modeliyle erişebiliriz.

Bu şekilde, makineleri namespace library'a koyabiliriz. foo, ADL aracılığıyla bulunabilir ve eğer (örneğin) ifade-şablon benzeri kodunuza işleç eklemeye başlarsanız, bunları çalışmak için main için el ile içe aktarmanız gerekmez.

derived, kitaplığınız için istemci kodu tarafından oluşturulan bir sınıftır, başka bir ad alanına gider. ()'u istediği gibi geçersiz kılar ve "sadece çalışır".

foo'unile daha az karşılaşılan bir örneği yerine geçmesi ve bu stilin avantajları belirginleşir. main, using library::operator+'a gerek kalmadan constexpr auto e = d+d; olur. yapılan

değişiklikler, () bir static_cast s kaldırmak kullanılarak Derived ulaşmak için base bir self() yöntem ilave edilmesi ve sahip foo yerine expr<base<D1>, base<D2>> bir expr<D1, D2> geri dönerler.

+0

Neden 'kendini' gerekli? – Columbo

+0

@columbo aynı kalıbı tekrar ve tekrar yazarak kaydeder mi? CRTP tabanından sıkça türetilmek istiyoruz. – Yakk

+0

@columbo ya da neden "foo" nun doğrudan "D1" ve "D2" yi almasını istemiyorsunuz? "" tabanının "D1" tabanının "" tabanını alması ve 'D1'e geri dönmek için' self() 'yi çağırması sfinae testinden daha kolaydır. Ve biz ADL alırız. – Yakk