void-pointer
void-pointer

Reputation: 14827

constexpr and CRTP: compiler disagreement

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

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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_casts in (), and having foo return an expr<D1, D2> instead of a expr<base<D1>, base<D2>>.

Upvotes: 2

Columbo
Columbo

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 cv2 D,” where D is a class derived (Clause 10) from B [..]. If the object of type “cv1 B” is actually a subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined.

And subsequently, [expr.const]/2 applies:

A conditional-expression e is a core constant expression unless the evaluation of e, 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

Tavian Barnes
Tavian Barnes

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

Related Questions