4

In this answer I wrote the C++17 code:

cout << accumulate(cbegin(numbers), cend(numbers), decay_t<decltype(numbers[0])>{});

This received some negative commentary about the nature of C++'s type association, which I'm sad to say that I agree with :(

decay_t<decltype(numbers[0])>{} is a very complex way to get a:

Zero-initialized type of an element of numbers

Is it possible to maintain the association with the type of numbers' elements, but not type like 30 characters to get it?

EDIT:

I've got a lot of answers involving the a wrapper for either accumulate or for extracting the type from numbers[0]. The problem being they require the reader to navigate to a secondary location to read a solution that is no less complex than the initialization code decay_t<decltype(numbers[0])>{}.

The only reason that we have to do more than this: decltype(numbers[0]) Is because the array subscript operator returns a reference:

error: invalid cast of an rvalue expression of type 'int' to type 'int&'

It's interesting that with respect to decltype's argument:

If the name of an object is parenthesized, it is treated as an ordinary lvalue expression

However, decltype((numbers[0])) is still just a reference to an element of numbers. So in the end these answers may be as close as we can come to simplifying this initialization :(

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • If you want a zero, just write it down like this: 0. There is no need to pile Pelion on Ossa. – n. m. could be an AI Mar 28 '16 at 20:55
  • @n.m. That is absolutely wrong sir. A `0` will define `accumulate`'s `init` as an `int`. So, for example if, we're working with `float numbers[]` the values will be truncated. [This example](http://ideone.com/A12xin), for instance, should output 11.2, but if I use a `0` it will output 10. – Jonathan Mee Mar 29 '16 at 00:31
  • Yes, you are right. I forgot std::accumulate is broken... – n. m. could be an AI Mar 29 '16 at 03:43
  • @n.m. Heh, I certainly wouldn't call that broken, so much as extremely useful. I frequently use `accumulate` to construct strings from vectors of numbers using a `to_string` in the lambda. Incidentally, Visual Studio 2015 fires a [C4244 warning](https://msdn.microsoft.com/en-us/library/th7a07tz.aspx) if there is a type mismatch. So for people who have their warning levels elevated, and are paying attention to them, they won't make this mistake. – Jonathan Mee Mar 29 '16 at 10:59
  • Well, an API that makes you write things like `decay_t(decltype(..))` is broken. Normal people shouldn't need to know any of this. In C++11 we could have encapsulated this inside accumulate but as of now we're stuck with the '03 API. – n. m. could be an AI Mar 29 '16 at 12:20
  • @n.m. I'm not arguing that this seems like a pretty good bit of complexity, but as I [asked here](http://stackoverflow.com/questions/36265109/adding-integers-to-arrays-in-c/36265411?noredirect=1#comment60163633_36265411) is there another strongly typed language that provides a better way to do this? C++ really is more or less my language, I'm certainly not familiar enough with anything else to know a way to associate type (outside a template.) I could be that C++ actually has the best way to do this? – Jonathan Mee Mar 29 '16 at 13:05
  • Haskell ;) You could do something like [this](http://ideone.com/XrFP5b) (just a rough idea) and combine any element type with any initial value, provided the types "behave". Will work (or I hope so) for all built-in arithmetic types and reasonably designed arithmetic classes (rational, bignum...) OTOH your design will easily overflow if you try to sum a `char` array. – n. m. could be an AI Mar 29 '16 at 15:16
  • @n.m. I'll have to look up Haskell syntax then. Anyway, your association works... like all the other solutions in here it looks to secondary functions to take care of the association :( I regret to say that in my opinion, as difficult as `decay_t{}` is to read I think that's as good as we got. – Jonathan Mee Mar 29 '16 at 16:20
  • Well, looking up Haskell *syntax* probably won't be too useful... – n. m. could be an AI Mar 29 '16 at 16:29
  • Now we have `reduce(begin(numbers), end(numbers))` since C++17. No ranges version, unfortunately. – L. F. Jun 05 '21 at 08:16

3 Answers3

2

While I would always choose to write a helper function as per @Barry, if numbers is a standard container, it will export the type value_type, so you can save a little complexity:

cout << accumulate(cbegin(numbers), cend(numbers), decltype(numbers)::value_type());

going further, we could define this template function:

template<class Container, class ElementType = typename Container::value_type>
constexpr auto element_of(const Container&, ElementType v = 0)
{
    return v;
}

which gives us this:

cout << accumulate(cbegin(numbers), cend(numbers), element_of(numbers, 0));
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
2

Personal preference: I find the decay_t, decltype and declval dance pretty annoying and hard to read.

Instead, I would use an extra level of indirection through a type-trait value_t<It> and zero-initialization through init = R{}

template<class It>
using value_t = typename std::iterator_traits<It>::value_type;

template<class It, class R = value_t<It>>
auto accumulate(It first, It last, R init = R{}) { /* as before */ }
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
1

I think the best you can do is just factor this out somewhere:

template <class It, class R = std::decay_t<decltype(*std::declval<It>())>>
R accumulate(It first, It last, R init = 0) {
    return std::accumulate(first, last, init);
}

std::cout << accumulate(cbegin(numbers), cend(numbers));

Or more generally:

template <class Range, class T =
        std::decay_t<decltype(*adl_begin(std::declval<Range&&>()))>>
T accumulate(Range&& range, T init = 0) {
    return std::accumulate(adl_begin(range), adl_end(range), init);
}

cout << accumulate(numbers);

where adl_begin is a version of begin() that accounts for ADL.

Sure, we technically still have all the cruft that you were trying to avoid earlier... but at least now you never have to look at it again?

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Ummm... What is ADL? – Jonathan Mee Mar 28 '16 at 18:52
  • @JonathanMee Argument-dependent lookup – Barry Mar 28 '16 at 19:00
  • Can you help me understand what `begin` and `end` have to do with [ADL](http://en.cppreference.com/w/cpp/language/adl)? Is this just an effort to get around the C++14 requirement? – Jonathan Mee Mar 28 '16 at 19:46
  • @JonathanMee If you just google those words together, you'll find several very relevant questions. – Barry Mar 28 '16 at 19:58
  • So the best resource I could find on [ADL in relationship to `begin`/`end`](http://stackoverflow.com/a/17564445/2642059) said: "For code that only uses Standard Containers or C-style arrays, std::begin() and std::end() could be called everywhere without introducing using-declarations" So are you just writing `adl_begin`/`adl_end` because I didn't qualify that `numbers` was a "Standard Container or C-style array"? – Jonathan Mee Mar 29 '16 at 12:18