Рассмотрим функцию, которая принимает один или несколько параметров (например, имена файлов). Чтобы сделать его универсальным, полезно написать его для общего диапазона итераторов:
template<class Iter>
void function(Iter first, Iter last)
{
// do something
}
Теперь мы можем вызвать его следующим образом, независимо от того, как мы храним аргументы:
WhateverContainer container;
function(std::begin(container), std::end(container));
Например, STL в значительной степени опирается на эту парадигму.
Теперь представьте, что мы хотим вызвать функцию с одним аргументом, который не хранится в контейнере. Конечно, мы можем написать:
const int value = 5;
std::vector<int> vec(1, value);
function(std::begin(vec), std::end(vec));
Но это решение кажется мне неуклюжим и расточительным.
Вопрос: Есть ли лучший способ создания единственной переменной, совместимой с диапазоном итераторов, с минимальными издержками?
Вы можете использовать указатели, на этот раз:
function(&value, &value + 1);
В общем коде std::addressof
вместо унарного оператора &
несколько безопаснее, в зависимости от вашего уровня паранойи.
Вы можете, конечно, обернуть это в перегрузку для более легкого использования:
template <class T>
decltype(auto) function (T &&e) {
auto p = std::addressof(e);
return function(p, p + 1);
}
Вы можете рассматривать это как массив из одного элемента на [Expr.unary.op] / 3:
function(&value, &value + 1);
В целях арифметики указателей ([expr.add]) и сравнения ([expr.rel], [expr.eq]) объект, который не является элементом массива, адрес которого берется таким образом, считается принадлежащим массиву с одним элементом типа Т.
Вы также можете перегрузка шаблон вашей функции function
для одноэлементный диапазон:
template<typename Iter>
void function(Iter first) {
return function(first, std::next(first)); // calls your original function
}
Таким образом, ваша оригинальная функция function
остается совместимым с диапазонами итераторов. Однако обратите внимание, что использование этой перегрузки с пустым диапазоном приведет к неопределенному поведению.
Для одного элемента value
Вы можете использовать перегрузку выше:
function(&value); // calls overload
С оператором &
может быть перегружен, рассмотрите также использование std::addressof
вместо &
как уже упоминалось в этот ответ.
Для диапазона, состоящего из одного элемента, вы также можете использовать приведенную выше перегрузку, для которой нужен только один итератор вместо пары итераторов:
const int value = 5;
std::vector<int> vec(1, value); // single-element collection
function(std::begin(vec)); // <-- calls overload
Я думаю, что я сделал бы это в два этапа:
Определите перегрузку функции шаблона, которая принимает контейнер, написанную в терминах версии итератора.
Определите прокси-класс, который обрабатывает ссылку на объект как массив размера 1.
Пример с ++ 17:
#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 });
}