Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring return type of templated member functions in CRTP

Is it possible to infer the return type of a templated member function in a CRTP base class?

While inferring argument types works well, it fails with the return type. Consider the example below.

#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;
}

This produces the following error:

  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.

My intuitive assumption is that if the compiler is capable of inferring type int for the argument to f, it should also work for the return bool, because both types are known at template instantiation time.

I tried using the trailing return type function syntax, but then failed to find a working expression to put in decltype.

EDIT 1

For the case where the function has one or more templated arguments, Dietmar Kühl provided a solution based on delaying template instantiation using on layer of indirection. Unfotunately, this does not work when a base class function does not have any argument, like this:

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

Attempts to use the same technique fail because there exist no dependent types. How does one handle this case?

EDIT 2

As pointed out by Johannes Schaub, C++11 features default template arguments, so it is always possible to make g dependent on an arbitrary type and then apply Dietmar's solution:

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

EDIT 3

This problem does not exist in C++14 anymore, since we have return type deduction for normal functions, allowing us to write simply:

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;
  }
};
like image 394
mavam Avatar asked Aug 22 '13 23:08

mavam


1 Answers

An extra indirection is your friend:

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);
    }
};
like image 60
Dietmar Kühl Avatar answered Oct 04 '22 08:10

Dietmar Kühl