16

I now want to make template that will push some elements to vectors and other types, that support push_back operators.

I can do it like this

template<typename T>
T fill(size_t n) {
    T v;
    //(1)
    for(size_t i = 0; i < n; ++i){
        v.push_back(generate_some_how());
    }
    return v;
}

It works. But now I want to improve speed for types that support it using v.reserve(n); instead of (1). But I want to still be able to compile this code for types that will not compile reserve

Is it some easy way to achieve this?

I know that I may specialize hard-coded types but it doesn't seem to be good.

C++11 is OK.

RiaD
  • 42,649
  • 10
  • 67
  • 110
  • I doubt it, but I don't know for sure. My guess is you will have to use template specialization – NG. Mar 08 '13 at 20:05
  • 4
    Not too hard.... you need to write a trait that detects the presence of the `reserve` member function with the correct signature. With that tool in your belt there are different approaches, you can write a `reserve_` template function that uses that trait to dispatch to either a call to `reserve()` or a no-op and call it from `// (1)` or you could use SFINAE either in the helper or the `fill` itself. I would try to use a helper function as most of the code in `fill` is the same. – David Rodríguez - dribeas Mar 08 '13 at 20:07
  • If you're using C++11 consider supporting the appropriate `emplace` call to replace `push_back`. – Captain Obvlious Mar 08 '13 at 20:20
  • 2
    Is there any reason you're doing this instead of just calling `v.reserve(n); std::generate_n(std::back_inserter(v), n, generate_some_how);` at the call site? (other than dropping 1 line of code) Or it could also be slightly more optimal: `v.resize(n); std::generate_n(v.begin(), n, generate_some_how);` – David Mar 08 '13 at 20:34
  • 2
    If you're expecting std containers, or containers that meet std [container requirements](http://en.cppreference.com/w/cpp/concept/Container) you should take a `typename T::size_type` not a `size_t` – David Mar 08 '13 at 20:44
  • @Dave: `resize` only works with defaultconstructable types, doesn't it? About `generate`: Yes, I need to change it. About `size_t`: good idea, thank you – RiaD Mar 08 '13 at 20:46
  • @CaptainObvlious, thank you, but since It will not be constructor call, there no reson for `emplace` (if I understand correctly what is it) – RiaD Mar 08 '13 at 20:53
  • If you are interested in detecting a container-requirements-conforming `reserve` method *only*, you can find a type trait in the answer to [this question](http://stackoverflow.com/q/14882588/2073257) I asked some time ago. – Daniel Frey Mar 09 '13 at 09:40

3 Answers3

11

An easy example using C++11:

template<class T>
auto maybe_reserve(T& v, size_t n, int)
    -> decltype(v.reserve(n), void())
{
  v.reserve(n);
}

template<class T>
void maybe_reserve(T&, size_t, long){}

template<typename T>
T fill(std::size_t n) {
    T v;
    maybe_reserve(v, n, 0);
    for(size_t i = 0; i < n; ++i){
        v.push_back(generate_some_how());
    }
    return v;
}

Live example. For explanations, take a look here.

Community
  • 1
  • 1
Xeo
  • 123,374
  • 44
  • 277
  • 381
  • A C++03 version is also possible, albeit more convolute and restrictive. – Xeo Mar 08 '13 at 20:20
  • didn't compile after change `resize` to `reserve` for `deque` [ideone](http://ideone.com/e7p8fv) – RiaD Mar 08 '13 at 20:29
  • @RiaD: That's a problem with GCC 4.7. Clang 3.2 and GCC 4.8 work correctly. GCC's bug can be worked around by changing the return type of the first overload to `decltype(v.reserve(n), void())`. – Xeo Mar 08 '13 at 20:34
  • 1
    Btw, maybe you could avoid the extra dummy argument? See this [example](http://liveworkspace.org/code/4sAas7$20) :) – Andy Prowl Mar 08 '13 at 20:49
  • @Andy: Could do, but meh, I like to be explicit about that. :) – Xeo Mar 08 '13 at 20:50
  • About avoiding extra type: If you will delete extra arguments and change `size_t` to `int` or `long long` in do-nothing-function it seems to work too. (but it'll fail if i would pass `int` instead of `size_t` i.e `maybe_reserve(v, 5)` – RiaD Mar 08 '13 at 21:07
6

A possible approach in C++11:

template<typename T, typename = void>
struct reserve_helper
{ static void call(T& obj, std::size_t s) { } };

template<typename T>
struct reserve_helper<T, decltype(std::declval<T>().reserve(0), void(0))>
{ static void call(T& obj, std::size_t s) { obj.reserve(s); } };

template<typename T>
T fill(std::size_t n)
{
    T v;
    reserve_helper<T>::call(v, 10);
    for(std::size_t i = 0; i < n; ++i){
        v.push_back(generate_somehow());
    }

    return v;
}

Here is a live example showing that the call to reserve() is simply skipped with a UDT that doesn't define any reserve() member function.

Andy Prowl
  • 114,596
  • 21
  • 355
  • 432
2

Not too hard.... you need to write a trait that detects the presence of the reserve member function with the correct signature. With that tool in your belt there are different approaches, you can write a reserve_ template function that uses that trait to dispatch to either a call to reserve() or a no-op and call it from // (1) or you could use SFINAE either in the helper or the fill itself. I would try to use a helper function as most of the code in fill is the same.

Detect if void reserve(std::size_t) member function exists in C++03:

template <typename T>
struct has_reserve {
   typedef char yes;
   typedef yes  no[2];
   template <typename U, U> struct ptrmbr_to_type;
   template <typename U> 
   static yes& test(ptrmbr_to_type<void (T::*)(std::size_t),&U::reserve>*);
   template <typename U> static no& test(...);
   static const bool value = sizeof(test<T>(0))==sizeof(yes);
};
David Rodríguez - dribeas
  • 192,922
  • 20
  • 275
  • 473