C++ CRTP和c++;1y返回类型扣除

C++ CRTP和c++;1y返回类型扣除,c++,c++11,crtp,c++14,return-type-deduction,C++,C++11,Crtp,C++14,Return Type Deduction,我最近在玩CRTP的时候,遇到了一个让我吃惊的东西,当我使用c++1y函数时,它的类型是推断的。以下代码起作用: template<typename Derived> struct Base { auto foo() { return static_cast<Derived*>(this)->foo_impl(); } }; struct Derived: public Base<Derived> {

我最近在玩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;
}
此代码不再工作,我收到以下错误(来自GCC 4.8.1):

||在“struct Base”的实例化中:|
|从这里开始需要|
|错误:从类型“Base*const”到类型“派生*”的静态\u转换无效|
||在函数“int main()”中:|
|错误:“struct-Derived”没有名为“foo”的成员|
我的问题是:为什么它不起作用?我可以写些什么来获得正确的返回类型而不依赖于自动返回类型扣除

而且,嗯。。。下面是一个。

为什么第一个示例有效(返回类型扣除)? 类模板的成员函数的定义仅在使用odr时隐式实例化(或显式实例化)。也就是说,通过从
Base
派生,您不会隐式地实例化函数体。因此,返回类型尚未推导

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

(*)不是“the”,而是“某些实例化点”。有几个


为什么第二个示例不起作用(尾部返回类型)? 我假设
Base::foo
的返回类型是
decltype
返回的表达式,但如果我修改函数
foo
,如下所示:

auto foo()
    -> decltype(static_cast<Derived*>(this)->foo_impl())
{
    return static_cast<Derived*>(this)->foo_impl();
}
尾随返回类型是成员函数声明的一部分;因此,它是周围类定义的一部分,从
Base
派生时需要实例化。此时,
Derived
仍然不完整,特别是
Derived::foo_impl
尚未声明


我可以写些什么来获得正确的返回类型而不需要 是否依赖自动退货类型扣除

现在这很棘手。我要说的是,这在标准中没有非常明确的定义,例如,参见

下面的示例演示了clang++3.4在
Base
中找不到
Derived
的成员:


我发现了一个解决方案,也许不是很漂亮,但我认为它符合标准

有人指出,这是非常有限的,因为它假设
foo_impl
可以在不访问派生或基本的其他部分的情况下实现。谢谢@DyP。我用另一种方法更新了这个答案。

无论如何,在回答为什么原始代码不起作用方面,我听从了其他人和@Dyp的回答。我学到了很多,描述得很好

用外行的话说,(在我有限的理解中!)基本问题是,当编译器看到这行代码时:

struct Derived:    public Base<Derived>

如果您使用的是GCC4.8,那么不必阅读所有内容,这可能会很有趣。@bamboon这是一个很好的观点。最好知道我的示例是否有效,然后^^“我认为如果没有c++14 auto,这是不可能做到的,派生的在尾部返回类型时没有
foo_impl
”我假设
Base::foo
的返回类型是返回表达式的
decltype
“:实际上
auto
返回类型推断更接近于“decltype”(例如,它剥离引用、顶级常量…)。现在,对于您的问题,可能是当“实例化”
foo
的“签名”(在
Base
的实例化中,从
派生的定义中请求)<代码>派生<代码>尚未完全定义,所以您不能在这里下注,但实际上您能用C++来编译吗?11@aaronman这是一个名称查找的问题;i、 e.在实例化
Base
时,可以在类模板专用化
Base
中访问类
派生的
的名称。我认为标准(依赖名称查找)并不完全清楚,但我认为不允许访问这些名称(未找到).对,我认为让解决方案更具“弹性”实际上是不可能的。你可以做一些类似于
模板
的事情,然后使用
typename常量::type
来表示
派生的
,其中
模板结构常量{using type=C;}--那么用什么来实例化
foo
并不重要。(马基雅维利代码可以为劫持和hijinks引入一个
constant
的专门化。)虽然它可以工作,但它假设
foo_impl
的返回类型始终是
int
,而它可能会在派生类的函数中发生变化:/它不会做出这样的假设。单词
int
仅出现在
foo\u impl
中的一个位置。你可以随意改变。您可以根据
foo_impl
中的返回语句将
int
替换为decltype。如果您不需要将
Base
作为基类来定义
foo_impl
@Dyp,这是一个有趣的解决方法,很好。这是一个很大的限制!也许应该取出
foo_impl
并将其定义为一个自由(友元)函数。然后它将与CRTP参数一起传递。在这种情况下,
notquitedrived
只是一种将有关
foo\u impl
的信息传递给Base的方法。我扩展了我的解决方案,使其能够更多地访问
派生的
template<typename Derived>
struct Base
{
    auto foo() -> decltype( std::declval<Derived&>().foo_impl() )
    {
        return static_cast<Derived*>(this)->foo_impl();
    }
};
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();
    }
};
struct Derived:    public Base<Derived>
template<typename Derived, typename TypeOfTheFriendFunction>
struct Base
{
    auto foo() -> typename std::function<TypeOfTheFriendFunction> :: result_type
    {
        return foo_impl_as_friend(static_cast<Derived*>(this) /*, any other args here*/);
    }
};

struct Derived;
auto foo_impl_as_friend(Derived * This /*, any other args here*/) -> std::string;

struct Derived:
    public Base<Derived, decltype(foo_impl_as_friend ) >
{
        private:
        void method_that_foo_impl_needs() { }   // Just to demonstrate that foo_impl can act as part of Derived

        friend decltype(foo_impl_as_friend) foo_impl_as_friend;
};

auto foo_impl_as_friend(Derived *This) -> std::string
{
        This -> method_that_foo_impl_needs();
        return "a string";
}