1

I have the following two functions:

class Leaf {...};
void SpitLeaves(std::string & sdata, std::vector<Leaf> const & leaves);
void SpitLeaves(std::string & sdata, std::set<Leaf> const & leaves);

The definition of these functions is identical.

It's an obvious candidate for templatizing the function.

However, despite a search, I cannot figure out how to properly declare the template function. From https://stackoverflow.com/a/4697356/368896 and others, I have tried signatures such as:

template <template<typename> class T>
void SpitLeaves(std::string & sdata, T<Leaf> const & leaves)
{...}

However, this gives a compiler error at the point I attempt to instantiate the template function:

std::string leaves_str;
std::vector<Leaf> leaves;
SpitLeaves<std::vector>(leaves_str, leaves);

... The error (VS 2013) is 'SpitLeaves' : template parameter list for class template 'std::vector' does not match template parameter list for template template parameter 'T'.

How do I properly declare the above template function?

Community
  • 1
  • 1
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181

3 Answers3

4

In C++11, you can use a variadic template template parameter:

template <template<typename...> class T>
void SpitLeaves(std::string & sdata, T<Leaf> const & leaves)

Now the compiler won't care how many template parameters can be taken by T, as long as T<Leaf> can be instantiated.

If you actually want to support a container with zero or more extra explicit template parameters, such as a custom allocator, you can do so:

template <template<typename...> class T, typename... TX>
void SpitLeaves(std::string & sdata, T<Leaf, TX...> const & leaves)
Oktalist
  • 14,336
  • 3
  • 43
  • 63
3

You can go with:

template<typename Container>
void SpitLeaves(std::string & sdata, Container const & leaves) { ... }

Call it like this:

std::vector<Leaf> leaves;
SpitLeaves("leaves", leaves); // template argument not necessary

The problem with your template template approach is that containers may take more than just one argument. For example vector takes two: the type of elements to store and the allocator to use. So in order to work you need this:

template< template<typename,typename> class T, typename A >
void SpitLeaves(std::string & sdata, T<Leaf,A> const & leaves) { ... }

If you use a generic type Container, you can just use the container and the compiler will complain if it is not compatible with your function.

Danvil
  • 22,240
  • 19
  • 65
  • 88
  • 1
    @DanNissenbaum, what advantages would such enforcement have? – eerorika Apr 02 '14 at 14:10
  • By looking at the function signature, it is more promptly grasped what the purpose of the function is. Also, I would like to know how to achieve this type of granularity in my use of templates, because I suspect it might be useful in the future. I'd like to know if it's possible with C++11! – Dan Nissenbaum Apr 02 '14 at 14:12
  • By enforcing the use of `Leaf`, it would also prevent any undesireable side effects if it were used with something that is not a `Leaf` even if the interface allowed it to build. – Dan Nissenbaum Apr 02 '14 at 14:15
  • Thanks for this. A follow-up question is then: Is it possible, in C++11, to take advantage of default template arguments in such a way as to be able to provide an alternative version of the declaration for `SpitLeaves` that does not require explicitly providing the second template argument? – Dan Nissenbaum Apr 02 '14 at 14:24
  • @DanNissenbaum, If the function compiles with other types, perhaps it is actually more generic than it's name implies? If it really isn't, you can document the fact that the template is supposed to be called with `Leaf` containers with a comment right above the declaration. To prevent compilation you can use `static_assert(std::is_same::value, "value_type must be Leaf");` (c++11). – eerorika Apr 02 '14 at 14:27
  • 1
    @user2079303 I generally agree. However, if the greater generality is needed, it could trivially be added at that time. This approach is consistent with the concept of "it is easier to build with higher granularity and then lessen the granularity later, than the reverse". But in any case, I just want to *know* how to do this in a simple, slick way, or on the other hand to know that there *is* no simple, slick way to do it. – Dan Nissenbaum Apr 02 '14 at 14:29
1

std::vector and std::set have more than 1 template argument.

So you should use something like:

template <template<typename...> class C, typename ... Args>
void SpitLeaves(std::string & sdata, const C<Leaf, Args...>& leaves)

An other approach is to take the container, and use SFINAE to restrict to correct type:

template <typename C>
typename std::enable_if<std::is_same<Leaf, typename C::value_type>::value>::type
SpitLeaves(std::string & sdata, const C& leaves)
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Instead of SFINAE you could also move the `is_same` condition into a `static_assert` in the function body. IMHO it's easier to read and would generate a better error message. – Oktalist Apr 02 '14 at 16:00
  • @Oktalist Unless there's another overload of `SpitLeaves` intended for non-containers; or you want to find out whether `SpitLeaves` can be called. – iavr Apr 02 '14 at 16:45
  • @iavr If there were another overload of `SpitLeaves` then I might consider tag dispatch. If you want to find out whether `SpitLeaves` can be called then a trait could be provided. I admit there may be cases where SFINAE would be the best choice. – Oktalist Apr 07 '14 at 15:51