0

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;
}
nyronium
  • 1,258
  • 2
  • 11
  • 25
  • It's a lot easier to obtain this information from `first` / `last` than it is to do what you propose. – Justin Dec 22 '17 at 21:56
  • 1
    If you are fine not using `auto` as a parameter in the lambda, your code will work – Justin Dec 22 '17 at 21:57

1 Answers1

4

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&&).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Great answer, do you mind elaborating what could lead to the 1% of type errors not catched? – nyronium Dec 23 '17 at 10:26
  • @nyron I did? Examples are in the brackets in the same paragraph. – Yakk - Adam Nevraumont Dec 23 '17 at 12:53
  • 1
    Or you want more words? Unassignable but constructible types being iterated over; that is easy to fix (replace domvertible trait with assignable trait at one spot). Plus arguments with strange copy/move constructor operations with the return type. I would have to play around to work out an example where the above would fail there. Includong the return type with itself. And if the return type binop with itself goes to something strange, I ignore that fact so long as it converts to the return type (the choice of one iteration is arbitrary) – Yakk - Adam Nevraumont Dec 23 '17 at 12:59
  • Thank you, now it's clear what you were talking about. – nyronium Dec 23 '17 at 19:42