Reputation: 14827
When expression templates are implemented using CRTP, the class at the top of the expression hierarchy uses base-to-derived downcasting in order to implement some of its operations. According to clang-3.5 (-std=c++1y
), this downcast should be illegal in constexpr
functions:
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 happily compiles the code. So who is right? If Clang is right, which C++14 restriction on constexpr
functions makes this downcasting illegal?
Here's the 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, "");
}
Upvotes: 9
Views: 939
Reputation: 275650
Here is a decent rework of your original code. UB is removed, and it extends nicely:
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, "");
}
Basically every base<X>
must be a X
. So when you store it, you store it as an X
, not a base<X>
. We can access the X
via base<X>::self()
in a constexpr
fashion.
By doing it this way, we can put the machinery into namespace library
. foo
can be found via ADL, and if you (for example) start adding operators to your expression-template like code, you won't have to manually import them for your main
to work.
Your derived
is a class created by client code for your library, so goes in another namespace. It overrides ()
as it pleases, and it "just works".
A less contrived example would replace foo
with operator+
, and the advantages of this style become apparent. main
becomes constexpr auto e = d+d;
without having to using library::operator+
.
The changes made are adding a self()
method to base
to access Derived
, using it to remove a static_cast
s in ()
, and having foo
return an expr<D1, D2>
instead of a expr<base<D1>, base<D2>>
.
Upvotes: 2
Reputation: 60999
For the operator()
in base
to do a valid static_cast
, the most-derived object that this
points to must be of type Derived
(or a subclass thereof). However, the members of e
are of type base<derived>
, not derived
itself. In the line
const noexcept { return m_a() + m_b(); }
m_a
is of type base<derived>
, and base<derived>::operator()
is called - with a most-derived object of type base<derived>
.
Thus the cast tries to cast *this
to a reference to object type that it doesn't actually refer to; That operation would have undefined behavior as [expr.static.cast]/2 describes:
An lvalue of type “cv1
B
,” where B is a class type, can be cast to type “reference to cv2D
,” whereD
is a class derived (Clause 10) fromB
[..]. If the object of type “cv1 B” is actually a subobject of an object of typeD
, the result refers to the enclosing object of typeD
. Otherwise, the behavior is undefined.
And subsequently, [expr.const]/2 applies:
A conditional-expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:(2.5) — an operation that would have undefined behavior
Instead, rewrite foo
as follows:
template <class D1, class D2>
constexpr auto foo(const D1& d1, const D2& d2)
noexcept { return expr<D1, D2>{d1, d2}; }
And the code works fine.
Upvotes: 10
Reputation: 12922
It seems to me that Clang is right in this case. The type of e
is const expr<base<derived>, base<derived>>
, so m_a
and m_b
have type base<derived>
, rather than derived
. In other words, you have sliced d
when copying it into m_a
and m_b
.
Upvotes: 5