constexpr和CRTP:编译器意见不一致

voi*_*ter 8 c++ templates language-lawyer constexpr c++14

当使用CRTP实现表达式模板时,表达式层次结构顶部的类使用从基础到派生的向下转换来实现其某些操作.根据clang-3.5(-std=c++1y),这个垂头丧气的constexpr功能应该是非法的:

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)(); }
Run Code Online (Sandbox Code Playgroud)

GCC愉快地编译代码.那么谁是对的?如果Clang是对的,哪些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, "");
}
Run Code Online (Sandbox Code Playgroud)

Col*_*mbo 9

对于operator()in base来做一个有效的static_cast,this指向的最派生对象必须是类型Derived(或其子类).但是,成员e属于类型base<derived>,而不是derived其本身.在线

const noexcept { return m_a() + m_b(); }
Run Code Online (Sandbox Code Playgroud)

m_a是类型base<derived>,并被base<derived>::operator()称为 - 具有类型的最派生对象base<derived>.
因此,强制转换试图转换*this为对它实际上没有引用的对象类型的引用; 该操作将具有未定义的行为,因为[expr.static.cast]/2描述:

类型为" cv1 B " 的左值,其中B是类类型,可以强制转换 为类型"引用cv2D ",其中DB[...] 派生的类(第10条).如果类型为" cv1 B"的对象实际上是类型对象的子对象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}; }
Run Code Online (Sandbox Code Playgroud)

代码工作正常.


Tav*_*nes 5

在我看来,Clang在这种情况下是正确的.该类型的econst expr<base<derived>, base<derived>>,如此m_am_b有型base<derived>,而不是derived.换句话说,在将其复制到和时,您已经切片了 .dm_am_b