推断CRTP中模板化成员函数的返回类型

Inferring return type of templated member functions in CRTP

本文关键字:成员 函数 返回类型 CRTP 推断      更新时间:2023-10-16

是否可以推断CRTP基类中模板化成员函数的返回类型

虽然推断参数类型效果很好,但它在返回类型时失败了。考虑下面的例子。

#include <iostream>
template <typename Derived>
struct base
{
  template <typename R, typename T>
  R f(T x)
  {
    return static_cast<Derived&>(*this).f_impl(x);
  }
};
struct derived : base<derived>
{
  bool f_impl(int x)
  {
    std::cout << "f(" << x << ")" << std::endl;
    return true;
  }
};
int main()
{
  bool b = derived{}.f(42);
  return b ? 0 : 1;
}

这会产生以下错误:

  bool b = derived{}.f(42);
           ~~~~~~~~~~^
crtp.cc:7:5: note: candidate template ignored: couldn't infer template argument 'R'
  R f(T x)
    ^
1 error generated.

我的直觉假设是,如果编译器能够推断出参数为f的类型int,那么它也应该适用于返回的bool,因为这两种类型在模板实例化时都是已知的。

我尝试使用尾随返回类型函数语法,但未能找到可放入decltype中的有效表达式。

编辑1

对于函数具有一个或多个模板化参数的情况,Dietmar Kühl提供了一种基于延迟使用间接层的模板实例化的解决方案。不幸的是,当基类函数没有任何参数时,这是不起作用的,比如:

template <typename R>
R g()
{
  return static_cast<Derived&>(*this).g_impl();
}

尝试使用相同的技术失败,因为不存在依赖类型。如何处理这个案子?

编辑2

正如Johannes Schaub所指出的,C++11具有默认的模板参数,因此总是可以使g依赖于任意类型,然后应用Dietmar的解决方案:

template <typename T = void>
auto g() -> typename g_impl_result<Derived, T>::type
{
  return static_cast<Derived&>(*this).g_impl();
}

编辑3

这个问题在C++14中已经不存在了,因为我们有正常函数的返回类型推导,允许我们简单地写:

template <typename Derived>
struct base
{
  template <typename T>
  auto f(T x)
  {
    return static_cast<Derived&>(*this).f_impl(x);
  }
  auto g()
  {
    return static_cast<Derived&>(*this).g_impl();
  }
};
struct derived : base<derived>
{
  bool f_impl(int x)
  {
    return true;
  }
  double g_impl()
  {
    return 4.2;
  }
};

一个额外的间接是你的朋友:

template <typename D, typename T>
struct f_impl_result
{
    typedef decltype(static_cast<D*>(0)->f_impl(std::declval<T>())) type;
};
template <typename Derived>
struct base
{
    template <typename T>
    auto f(T x) -> typename f_impl_result<Derived, T>::type
    {
        return static_cast<Derived&>(*this).f_impl(x);
    }
};