0

A typical function template signature:

template<typename Iterator, typename T>
T fn(Iterator first, Iterator last, T init)
{
    T result;
    // ...
    return result;
}

The problem is, when I call it like this:

std::vector<long> data(1000,1);
fn(data.begin(), data.end(), 0);

or any other way without explicitly calling

fn<std::vector<long>::iterator, long>(data.begin(), data.end(),0);

then the type of T is an int, and there is a risk of overflow and bad results in fn.

So how do I specialize fn so that the call to

fn(data.begin(), data.end(), 0);

is unambigous and results in T to be set to Iterator::value_type? The choice

template<Iterator>
typename iterator_traits<Iterator>::value_type fn(Iterator first, Iterator last, typename iterator_traits<Iterator>::value_type init)
{
    //...
}

results in an ambiguous call error from g++/clang++.

EDIT:

I see my mistake now and the above code works with @Lightness Races suggestion below. Thanks for the help.

Charles Pehlivanian
  • 2,083
  • 17
  • 25
  • I can't replicate the ambiguous call. Can you show us? – Lightness Races in Orbit Jan 09 '14 at 16:36
  • Note: forcing the type `T` to the value_type of the iterator may also result in overflow. Think about `std::accumulate` with `std::vector::iterator`, if we use `init` as '\0' versus `0`... – Jarod42 Jan 09 '14 at 16:53
  • @Jarod42: Yeah, you can't get around that by guessing with types. If the function body does something like `std::accumulate` then it should (somehow) ensure that it's got enough room to work with regardless. A specialisation for integral types that performs promotion would probably do. – Lightness Races in Orbit Jan 09 '14 at 17:19

2 Answers2

2

Why not simply pass a long?

fn(data.begin(), data.end(), 0L);

Beyond that you could probably do something like this:

#include <type_traits>

template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type fn(
   Iterator first,
   Iterator last,
   typename std::iterator_traits<Iterator>::value_type init
);

Live demo

I don't really see how you're at risk of an overflow, though.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

As:

0 is an int
0L is a long

You just have to call the method with the correct argument:

  • Use directly the correct literal

as:

fn(data.begin(), data.end(), 0L);
  • Or, you may use an intermediate variable

as:

long init = 0; // result in 0L

fn(data.begin(), data.end(), init);
  • or you may cast the value:

as:

fn(data.begin(), data.end(), static_cast<long>(0));

One way to write fn to avoid to call it correctly is

template<typename Iterator>
auto fn(Iterator first, Iterator last, typename std::decay<decltype(*first)>::type init)
    -> typename std::decay<decltype(*first)>::type;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Ok but I want to kind of prevent this from happening to myself or someone else. The real problem to me is that I can't specialize to make a proper `fn` now that this one is defined. The second template parameter is completely superfluous - the writer inteneded it always to be a `Iterator::value_type` , and I want to get rid of it. – Charles Pehlivanian Jan 09 '14 at 16:31