2015-01-13 3 views
8

Когда шаблоны выражений реализованы с использованием CRTP, класс в верхней части иерархии выражений использует базовое преобразование вниз для реализации некоторых своих операций. Согласно лязг-3.5 (-std=c++1y), это опущенными должно быть незаконным в constexpr функции:constexpr и CRTP: несогласие компилятора

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)(); } 

НКУ счастливо compiles the code. Так кто же прав? Если прав Клэнг, какое ограничение C++ 14 на constexpr делает это затухание незаконным?

Вот MWE:

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, ""); 
} 

ответ

9

Для operator() в base, чтобы сделать правильный static_cast, наиболее полученный из объекта, который указывает на this должен быть типа Derived (или подкласса их). Однако элементы e имеют тип base<derived>, а не derived. В строке

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

m_a имеет типа base<derived> и base<derived>::operator() называется - с наиболее производным объектом типа base<derived>.
Таким образом, бросок пытается отличить *this от ссылки на тип объекта, на который он фактически не ссылается; Эта операция будет иметь неопределенное поведение, как [expr.static.cast]/2 описывает:

Именующее выражение типа «cv1B», где В представляет собой тип класса, может быть приведен к типу « ссылки до cv2D, где D - класс , полученный (статья 10), от B [..]. Если объект типа «CV1 В» на самом деле является подобъект объекта типа D, результат относится к вмещающей объект типа D. В противном случае поведение не определено.

И затем, [expr.const]/2 применяется:

условно-выражениеe является постоянным выражением ядра, если оценка e, следуя правилам абстрактный машина (1.9), будет оценивать одно из следующих выражений:

(2.5) - это операция, которая будет иметь неопределенное поведение

Вместо этого перепишет foo следующим образом:

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

И код works fine.

5

Мне кажется, что Clang является правильным в данном случае. Тип e составляет const expr<base<derived>, base<derived>>, поэтому m_a и m_b имеют тип base<derived>, а не derived. Другими словами, у вас есть slicedd при копировании его в m_a и m_b.

+0

Я думаю, что искатель ищет стандартные ссылки. – Columbo

+0

@Columbo Я попытаюсь найти некоторые. Но как правило, большинство (всех?) Вещей, которые являются неопределенным поведением во время выполнения, являются ошибками в оценках 'constexpr'. –

+0

Я думал, что код в порядке, дайте мне поближе посмотреть – Columbo

2

Вот достойная доработка вашего исходного кода. UB удаляется, и он проходит красиво:

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, ""); 
} 

В основном каждый base<X>должен быть X. Поэтому, когда вы храните его, вы храните его как X, а не base<X>. Мы можем получить доступ к X по телефону base<X>::self() по телефону constexpr.

Делая это таким образом, мы можем поставить оборудование в namespace library. foo можно найти через ADL, и если вы (например) начнете добавлять операторов к вашему шаблону выражения, например, код, вам не придется вручную импортировать их для вашего main для работы.

Ваш derived - это класс, созданный клиентским кодом для вашей библиотеки, поэтому переходит в другое пространство имен. Он переопределяет (), как ему заблагорассудится, и он «просто работает».

Менее надуманный пример заменит foo на operator+, и преимущества этого стиля станут очевидными. main будет constexpr auto e = d+d; без необходимости using library::operator+.

Изменения, сделанное добавление метода self() к base для доступа к Derived, используя его, чтобы удалять их static_cast с в (), и имеющий foo возвращает expr<D1, D2> вместо expr<base<D1>, base<D2>>.

+0

Почему «сам» необходим? – Columbo

+0

@columbo позволяет печатать один и тот же список снова и снова? Мы хотим получить производные от базы CRTP довольно часто. – Yakk

+0

@columbo или вы спрашиваете, почему бы просто не сделать 'foo' брать' D1' и 'D2' напрямую? Проще, чем тест sfinae, что 'base ' является базой 'D1', это просто взять' base ' и вызвать 'self()', чтобы вернуться к 'D1'. И мы получаем ADL. – Yakk

Смежные вопросы