nyronium
nyronium

Reputation: 1278

Constraint template parameter depending on passed functor

I want to constraint a template parameter depending on the functor passed. Consider this FoldLeft function, from some container type:

template<typename F, typename R>
R FoldLeft(F&& functor, R initialValue) {
    R r = initialValue;
    /* assume that this is a range of uint64_t's */
    while (first != last) {
        r = std::forward<F>(functor)(r, *(first++));
    }
    return r;
}

This function can be called like this:

auto sum = FoldLeft([](uint64_t i, auto& e) { return e + i; }, 0);

Here, the problem is that R is deduced from the initialValue parameter, which is 0 in this case and thus leads to int. Similarly decltype(sum) also gives int.

I want to have R deduced as the return type of the functor, which may be a lambda or any other callable type. I already tried using the method from this answer, but always run into this error:

error:  decltype cannot resolve address of overloaded function
struct function_traits
       ^~~~~~~~~~~~~~~
note:   substitution of deduced template arguments resulted in errors seen above

The code for my attempt (fuction_traits copied from the linked answer):

template<typename T>
using LamRet = typename function_traits<T>::result_type;

template<typename F>
LamRet<F> FoldLeft(F&& functor, LamRet<F> initialValue) {
    LamRet<F> r = initialValue;
    /* assume that this is a range of uint64_t's */
    while (first != last) {
        r = std::forward<F>(functor)(r, *(first++));
    }
    return r;
}

Upvotes: 0

Views: 144

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275750

Function traits as described are, in my experience, nearly useless, and using them gets in binds like this, because callables in C++ don't have traits like function traits claim they have.

(The one large exception is where you are doing almost a problem specific sublanguage and intentionally cooperating with the traits in order to invoke DRY and not have to repeat types in two spots).

Only a subset of callables have such traits only. And the more you write C++14 and C++17 style lambdas, the fewer the callables qualify.

Do determine the return value, you need to know what the type you are iterating over is. Then examine decltype( lambda( argument, iterated_type ) ) (which can also be written as a result_of_t or invoke_result_t template type).

Suppose your iterated type is T, and your argument is A:

template<class F, class A>
using LamRet = std::decay_t<std::result_of_t<F&&( A&&, T& )>>;

Then we can check our lambda argument type with:

template<class F, class A>
using LamArgGood = std::is_convertible< A&&, LamRet<F, A> >;

and

template<class F, class A>
using LamRetGood = std::is_convertible< LamRet<F, A>, LamRet< F, LamRet<F, A > >;

which ensures that the return type of the iterations works.

template<class F, class A,
  class dA = std::decay_t<A>,
  std::enable_if_t< LamArgGood<F, dA>{} && LamRetGood<F, dA>{}, bool> =true
>
LamRet<F, dA> FoldLeft(F&& functor, A&& initialValue) {
  LamRet<F, dA> r = std::forward<A>(initialValue);
  /* assume that this is a range of uint64_t's */
  while (first != last) {
    r = std::forward<F>(functor)(r, *(first++));
  }
  return r;
}

this isn't quite right, but will catch 99% of type errors. (I assign, not construct, in the iteration; and I convert from A&& to LamRet, not dA&&).

Upvotes: 4

Related Questions