CRTP和c ++ 1y返回类型推导

Mor*_*enn 15 c++ crtp c++11 return-type-deduction c++14

我最近在玩CRTP的时候遇到的东西让我感到惊讶,因为它与c ++ 1y函数一起使用,其类型是推导出来的.以下代码有效:

template<typename Derived>
struct Base
{
    auto foo()
    {
        return static_cast<Derived*>(this)->foo_impl();
    }
};

struct Derived:
    public Base<Derived>
{
    auto foo_impl()
        -> int
    {
        return 0;
    }
};

int main()
{
    Derived b;
    int i = b.foo();
    (void)i;
}
Run Code Online (Sandbox Code Playgroud)

我假设返回类型Base<Derived>::foodecltype返回的表达式的一个,但如果我修改这样的函数foo:

auto foo()
    -> decltype(static_cast<Derived*>(this)->foo_impl())
{
    return static_cast<Derived*>(this)->foo_impl();
}
Run Code Online (Sandbox Code Playgroud)

此代码不再起作用,我收到以下错误(来自GCC 4.8.1):

||In instantiation of 'struct Base<Derived>':|
|required from here|
|error: invalid static_cast from type 'Base<Derived>* const' to type 'Derived*'|
||In function 'int main()':|
|error: 'struct Derived' has no member named 'foo'|
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么不起作用?我可以写什么来获得正确的返回类型而不依赖于自动返回类型扣除?

而且,嗯......这是一个现实的例子.

dyp*_*dyp 12

为什么第一个例子有效(返回类型推导)?

类模板的成员函数的定义仅在odr-used(或显式实例化)时被隐式实例化.也就是说,通过派生Base<Derived>,您不会隐式实例化函数体.因此,还没有推断出返回类型.

在(*)实例化点,Derived完成,Derived::foo_impl声明,返回类型推断可以成功.

(*)不是"the",而是"某些实例化点".有几个.


为什么第二个例子不起作用(trailing-return-type)?

我假设返回类型Base<Derived>::foodecltype 返回的表达式的一个,但是如果我foo像这样修改函数:

所述尾返回类型是成员函数的声明的一部分; 因此,它是周围类定义的一部分,需要在派生时进行实例化Base<Derived>.此时,Derived仍然是不完整的,具体Derived::foo_impl尚未宣布.


我可以写什么来获得正确的返回类型而不依赖于自动返回类型扣除?

现在这很棘手.我会说标准中没有明确定义,例如看到这个问题.

这是一个演示clang ++ 3.4没有找到Derived内部成员的例子Base<Derived>:

template<typename Derived>
struct Base
{
    auto foo() -> decltype( std::declval<Derived&>().foo_impl() )
    {
        return static_cast<Derived*>(this)->foo_impl();
    }
};
Run Code Online (Sandbox Code Playgroud)

declval不需要完整的类型,所以错误消息是,有没有foo_implDerived.


有一个黑客,但我不确定它是否合规:

template<typename Derived>
struct Base
{
    template<class C = Derived>
    auto foo() -> decltype( static_cast<C*>(this)->foo_impl() )
    {
        static_assert(std::is_same<C, Derived>{}, "you broke my hack :(");
        return static_cast<Derived*>(this)->foo_impl();
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 为了使解决方法更具"弹性",您可以执行类似`template <typename Dependent = void>`的操作,然后使用`typename constant <Derived,Dependent> :: type`来表示`Derived`,其中`template <typename C, typename ...忽略> struct constant {using type = C; `` - 然后将`foo`实例化为什么并不重要.(Machiavellian代码可以为劫持和hijinks引入`常量`的特化.) (4认同)