22

Consider a function that accepts one or more parameters (e.g. file names). In order to make it versatile, it is advantageous to write it for a general iterator range:

template<class Iter>
void function(Iter first, Iter last)
{
  // do something
}

Now we can invoke it in the following way, independently of how we store the arguments:

WhateverContainer container;
function(std::begin(container), std::end(container));

For example, the STL relies heavily on this paradigm.

Now, imagine we want to invoke the function with a single argument that is not stored in a container. Of course we can write:

const int value = 5;
std::vector<int> vec(1, value);
function(std::begin(vec), std::end(vec));

But this solution seems clumsy and wasteful to me.

Question: Is there a better low-overhead way of creating an iterator-range-compatible representation of a single variable?

piripiri
  • 1,925
  • 2
  • 18
  • 35
  • actually I think i misread your example, but anyhow now it is more clear – 463035818_is_not_an_ai May 04 '18 at 11:29
  • 7
    out of curiosity: what function that works on a range can you use in a meaningful way on a single element? – 463035818_is_not_an_ai May 04 '18 at 11:30
  • 4
    E.g. a function that can process either one or multiple files given by their names might be a use case. – piripiri May 04 '18 at 11:32
  • btw by no means i wanted to imply that there would be no use case for this – 463035818_is_not_an_ai May 04 '18 at 11:35
  • Sure, it's a valid question. Whenever the single elements in the range are changed, applying the function to a single argument might also be interesting. – piripiri May 04 '18 at 11:37
  • 4
    @piripiri Not sure if that's the very best design, though, *possibly* better to have that function just operating on one single file and using [`std::for_each`](http://en.cppreference.com/w/cpp/algorithm/for_each) on ranges. That might *not always* be suitable, but it's at least an alternative always worth considering... – Aconcagua May 04 '18 at 11:38
  • @Aconcagua Good point, if the arguments are treated separately anyway that is a good alternative. – piripiri May 04 '18 at 11:41
  • 8
    @piripiri, If the arguments of a function call are independent, keep in mind that keeping it to a single element at a time allows for things like using parallel algorithms with no extra work. – chris May 04 '18 at 11:43

4 Answers4

33

You can use pointers, for once:

function(&value, &value + 1);

In generic code, std::addressof instead of the unary operator & is somewhat safer, depending on your level of paranoia.

You can of course wrap this in an overload for easier use:

template <class T>
decltype(auto) function (T &&e) {
    auto p = std::addressof(e);
    return function(p, p + 1);
}
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • 1
    Could you elaborate on the advantages of `std::addressof`? – piripiri May 04 '18 at 11:46
  • 6
    See my answer [Is there any advantage of using std::addressof() function template instead of using & (address of) operator in C++?](//stackoverflow.com/a/32680218) – Baum mit Augen May 04 '18 at 11:46
13

You can treat it like an array of one element per [expr.unary.op]/3:

function(&value, &value + 1);

For purposes of pointer arithmetic ([expr.add]) and comparison ([expr.rel], [expr.eq]), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T.

chris
  • 60,560
  • 13
  • 143
  • 205
  • Maybe `std::begin(value)`, `std::end(value)` could be modified to return `&value`, `&value + 1` in this case. – Lingxi May 04 '18 at 11:45
  • 2
    @Lingxi, Perhaps, but it isn't a very common case and would need to detect a single value vs. a container. This can be hard in general. For example, imagine you want to pass a single container. – chris May 04 '18 at 11:47
6

You can also overload your function template function for a single-element range:

template<typename Iter>
void function(Iter first) {
    return function(first, std::next(first)); // calls your original function
}

This way, your original function function remains compatible with iterator ranges. Note, however, that using this overload with an empty range will result in undefined behavior.


For a single element, value, you can use the overload above:

function(&value); // calls overload

Since operator & may be overloaded, consider also using std::addressof instead of &, as already mentioned in this answer.


For a range consisting of a single element, you can use the overload above as well, which only needs a single iterator instead of an iterator pair:

const int value = 5;
std::vector<int> vec(1, value); // single-element collection
function(std::begin(vec)); // <-- calls overload
JFMR
  • 23,265
  • 4
  • 52
  • 76
2

I think I'd do this in two steps:

  1. Define a overload of the template function that takes a container, written in terms of the iterator version.

  2. Define a proxy class which treats an object reference as an array of size 1.

c++17 example:

#include <iterator>
#include <type_traits>
#include <vector>
#include <iostream>

// proxy object
template<class T>
struct object_as_container
{
    using value_type = T;
    using iterator = T*;
    using const_iterator = std::add_const_t<T>;

    object_as_container(value_type& val) : object_(val) {}

    const_iterator begin() const { return std::addressof(object_); }
    iterator begin() { return std::addressof(object_); }

    const_iterator end() const { return std::next(begin()); }
    iterator end() { return std::next(begin()); }

private:
    value_type& object_;
};

// our function in terms of iterators    
template<class Iter> void func(Iter first, Iter last)
{
    while(first != last)
    {
        std::cout << *first++;
    }
}

// our function in terms of containers
template<class Container> void func(Container&& cont)
{
    func(cont.begin(), cont.end());
}

int main()
{
    const int value = 5;
    func(object_as_container(value));
    func(std::vector { 1,2,3,4,5 });
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142