我最近在玩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;
}
我假设Base<Derived>::foo
的返回类型是返回的表达式的decltype
,但如果我像这样修改函数foo
:
auto foo()
-> decltype(static_cast<Derived*>(this)->foo_impl())
{
return static_cast<Derived*>(this)->foo_impl();
}
此代码不再有效,我收到以下错误(来自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'|
我的问题是:为什么不起作用?我可以写什么来获得正确的返回类型而不依赖于自动返回类型推导?
而且,嗯......这是一个live example。
答案 0 :(得分:12)
类模板的成员函数的定义仅在odr-used(或显式实例化)时被隐式实例化。也就是说,通过从Base<Derived>
派生,您可以不隐式实例化函数体。因此,返回类型仍未尚未推断。
在实例化的(*)点,Derived
完成,声明Derived::foo_impl
,返回类型推断可以成功。
(*)不是“the”,而是“某些实例化点”。有几个。
我认为
Base<Derived>::foo
的返回类型是decltype
返回的表达式,但如果我像这样修改函数foo
:
trailing-return-type 是成员函数声明的一部分;因此,它是周围类定义的一部分,在从Base<Derived>
派生时需要对其进行实例化。此时,Derived
仍然不完整,特别是Derived::foo_impl
尚未宣布。
我可以写什么来获得正确的返回类型 依靠自动退货类型扣除?
现在这很棘手。我会说标准中没有明确定义,例如见this question。
这是一个示例,演示了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();
}
};
declval
不需要完整的类型,因此错误消息是foo_impl
中没有Derived
。
有一个黑客,但我不确定它是否合规:
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();
}
};
答案 1 :(得分:1)
我发现了一个解决方案,也许不是很漂亮,但我认为它非常符合标准。
有人指出,这是非常有限的,因为它假定foo_impl
可以在不访问Derived或Base的其他部分的情况下实现。谢谢@DyP。我用另一种方法更新了这个答案。
无论如何,在回答原始代码不起作用的原因方面,我会推荐给其他所有人和@Dyp的答案。我学到了很多,很好描述。
基本问题,在外行人看来,(在我有限的理解中!),是当编译器看到这一行时:
struct Derived: public Base<Derived>
它立即想要/需要知道有关Base<Derived>
的部分/全部信息,即使它还没有看到定义foo_impl
的以下行。
解决方案是将foo_impl
移动到另一个名为NotQuiteDerived
的类中。然后Derived
继承了Base<...,...>
以及foo_impl
。这允许我们在引入Derived
之前放置Base
。然后我们需要Base
中的第二个模板类型参数。无论如何,代码可以说明一切! :击>
我已将此更改为更简单,可能稍微更好的方法。 Derived
不需要查看foo_impl
的所有内容,而是foo_impl
的签名。这可以与CRTP参数一起传递。
现在的另一种方法比上一种方法更灵活,因为它允许Derived
拥有对Derived
的更多访问权限,并且就像它实际上是foo_impl
的方法一样。我们可以在Derived
之前立即声明struct Derived: ...
为foo_impl
的朋友。这允许实现Base
以查看所有内容的完整定义,并允许foo_impl
返回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";
}
的返回类型。
{{1}}