Qqwy
Qqwy

Reputation: 5649

C++: Template argument deduction when passing template functions as argument to other template functions

I have the following snippet of example code:

The idea is that there is a container class, here called Box, and that we might want to create new versions of this container by mapping a function over its current contents.

#include <iostream>
#include <tuple>

template <typename TContent>
class Box
{
  TContent d_content;

  public:

  Box(TContent content)
  :
    d_content(content)
  {}

  TContent content() const
  {
    return d_content;
  }

  template <typename Function>
  auto transform(Function fun) -> decltype(Box{fun(d_content)})
  {
    return Box{fun(d_content)};
  };

};

template <typename TElem>
std::tuple<TElem, TElem> toTuple(TElem thing)
{
  std::cout << "Transforming " << thing << "to tuple.\n";
  return std::make_tuple(thing, thing);
}

int main() {
  std::cout << "Hello World!\n";

  Box<int> mybox{10};
  Box<int> box2 = mybox.transform([](int content){ return content * 2;});
  std::cout << "Transformed box: " << box2.content() << '\n';
  Box<std::tuple<int, int>> box3 = mybox.transform(&toTuple); // <- Template argument deduction/substitution fails here!
  std::cout << "Transformed box: " << std::get<0>(box3.content()) << '\n';
}

Try it out here

At line 42, where box3 is created, template argument deduction/substitution fails:

main.cpp: In function 'int main()':
main.cpp:42:60: error: no matching function for call to 'Box<int>::transform(<unresolved overloaded function type>)'
   Box<std::tuple<int, int>> box3 = mybox.transform(&toTuple);
                                                            ^
main.cpp:22:8: note: candidate: template<class Function> decltype (Box<TContent>{fun(((Box<TContent>*)this)->Box<TContent>::d_content)}) Box<TContent>::transform(Function) [with Function = Function; TContent = int]
   auto transform(Function fun) -> decltype(Box{fun(d_content)})
        ^~~~~~~~~
main.cpp:22:8: note:   template argument deduction/substitution failed:
main.cpp:42:60: note:   couldn't deduce template parameter 'Function'
   Box<std::tuple<int, int>> box3 = mybox.transform(&toTuple);
                                                            ^

exit status 1

This seems to be the case when attempting to pass a template function (function template?) to a function that itself expects a template argument parameter.

The only way to avoid this that I've found thus far is to wrap all template functions in lambdas or other, non-template functions. This is of course suboptimal, as it introduces a lot of boilerplate.

Why is template argument deduction failing in this case, and is there a way to alter the code of the Box class (and/or its transform member function) to ensure that template argument deduction does work?

(The given code is C++11 as repl.it does not yet support c++14. The main difference in C++14 would be that the trailing return type of transform can be omitted. The error, though, remains the same. I am happy with solutions that (only) work in C++14 as well.)

Upvotes: 2

Views: 168

Answers (1)

Barry
Barry

Reputation: 303537

In your example here:

template <typename Function>
auto transform(Function fun) -> decltype(Box{fun(d_content)})

Box is a class template, not a class. Inside the class, Box is the injected-class-name, it always refers specifically to Box<TContent>. So if you need to change the type (as transform may very well need to do), this won't work. You need to specify which Box you want based on what Function is:

template <class Function,
  class U = std::result_of_t<Function&(TContent)>>
Box<U> transform(Function fun)
{
  return Box<U>{fun(d_content)}; // or even better, Box<U>{std::invoke(fun, d_content)}
};

The second problem is when you call it:

Box<std::tuple<int, int>> box3 = mybox.transform(&toTuple);

toTuple is a function template, not a function. You can't pass it to another function template as it doesn't have a type, it can't be deduced. As before, you need to specify which toTuple you want:

Box<std::tuple<int, int>> box3 = mybox.transform(toTuple<int>);

Or wrap the whole thing in a lambda (this is a simplified implementation which doesn't care about copies, references, or SFINAE):

Box<std::tuple<int, int>> box3 = mybox.transform([](auto x) { return toTuple(x); });

Upvotes: 2

Related Questions