Автоматически конвертировать указатель / итератор в const в функции шаблона

Я пытаюсь создать функцию, которая работает как с указателями, так и с итераторами (я хочу иметь возможность проверять итераторы над коллекциями во время тестирования и гибкость в использовании массивов для представления библиотеки C без переноса). Я хочу сделать это, используя как можно меньше кода, не полагаясь на функции Boost или C++11. Хорошо, я не, моя спецификация требований делает.

Пока что решение довольно простое: шаблон функции, параметризованный на итераторе. Проблема в том, что я хочу, чтобы итератор ссылался на константные значения; частично, чтобы избежать глупых ошибок (изменение чего-то, что является строго входным значением) и частично, чтобы позволить компилятору использовать постоянство для оптимизации (значения используются в узких циклах, где каждая оптимизация приветствуется.

Рассмотрим этот пример:

void foo(vector<int>::const_iterator it)
{
        //it[5] = 7; // 1
        int tmp = it[5];
}

void bar(const int *it)
{
        //it[5] = 7; // 2
        int tmp = it[5];
}

template<class T>
void baz(T it)
{
        it[5] = 7; // 3
        int tmp = it[5];
}

template<class T>
void qux(T it)
{
        //it[5] = 7; // 4
        int tmp = it[5];
}

template<class T>
void quux(const T it)
{
        it[5] = 7; // 5
        int tmp = it[5];
}

vector<int> a(10);
const vector<int> b(10);
int c[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int d[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foo(a.begin());
foo(b.begin());
bar(c);
bar(d);
baz(a.begin());
//baz(b.begin()); // 5
baz(c);
//baz(d); // 6
qux(a.begin());
qux(b.begin());
qux(c);
qux(d);
quux(a.begin()); // 7
//quux(b.begin()); // 8
quux(c);
//quux(d); // 9

Фу и бар работают как задумано; Я могу читать значения, и если я пытаюсь комментировать строки 1 или 2 обратно, я получаю ошибку компиляции. Кроме того, я могу вызывать функции как с постоянными, так и с неконстантными значениями. Это требует дублирования кода, хотя.

Баз избегает этого, используя простой шаблон. Я могу назвать это используя указатель и итератор. Однако, если я никогда не вызову его с помощью константного значения (11 и 5 закомментированы), мне разрешено присваивать значение, на которое указывает (п. 3 не приводит к ошибке). Удаление назначения (qux, l. 4) позволяет мне передавать значения const и non-cost одинаково. Это в принципе хорошо (я мог бы отлавливать ошибки, используя тестовый пример), но это не дает компилятору понять, что значение должно считаться константным и соответственно применять оптимизации. Я бы предпочел, чтобы вызывающая сторона не беспокоилась о константности в этом случае (тем более что забывание константности не является ошибкой и просто "необъяснимо" приводит к более медленному коду).

Теперь я хотел бы что-то вроде quux, кроме, знаете, работы. Я хочу что-то с поведением, похожим на foo и bar, но без дублирования кода. Проблема в том, что я получаю константный указатель / итератор для неконстантного целого числа, а не указатель / итератор (константный или нет) для константных целых чисел. Я могу оставить в строке 5 или (строки 8 и 9), но не оба. Я хотел бы, чтобы компилятор принудительно установил, что если я уйду в любой из строк 7-9, строка 5 будет ошибкой компиляции.

Я знаю, я мог бы исправить это с помощью специализаций шаблонов и / или перегрузок функций (ниже), но это требует от меня этого (и если я забуду об этом, я могу оказаться в случае baz, за исключением случаев, когда тестовый пример не найден, чтобы найти ошибку).). Код должен легко перемещаться, временно копироваться для альтернативных реализаций и т. Д., Но при этом иметь возможность сделать его эффективным и надежным. Это также заставляет меня узнать больше о моей коллекции.

template<class T>
void quux_(T it)
{
        it[5] = 7; // 5
        int tmp = it[5];
}

void quux(const int* it) { quux_<const int*>(it); }
void quux(std::vector<int>::const_iterator it) { quux_<std::vector<int>::const_iterator>(it); }

Я также мог бы исправить это путем явного преобразования в соответствующее значение const (хотя преобразование и итератор в соответствующий const_iterator кажется немного сложным, если вы не знаете коллекцию, а C++ не очень доволен даже параметризованными коллекциями, хотя я предполагаю, что это можно исправить). Это, однако, накладывает немало усилий на котел (у меня есть до 4 таких параметров и десятки таких функций). Из двух альтернатив это мой любимый вариант, хотя я бы хотел что-то с простотой foo и bar.

// template<class U> struct constify<typename std::vector<U>::iterator> { typedef typename std::vector<U>::const_iterator type; };
template<> struct constify<typename std::vector<int>::iterator> { typedef typename std::vector<int>::const_iterator type; };
template<> struct constify<std::vector<int>::const_iterator> { typedef std::vector<int>::const_iterator type; };
template<class U> struct constify<U*> { typedef const U* type; };
template<class U> struct constify<const U*> { typedef const U* type; };

template<class T>
void quux(T p_it)
{
        typename constify<T>::type it = p_it;
        //it[5] = 7; // 5
        int tmp = it[5];
}

У кого-нибудь есть элегантный способ добиться этого? Если единственным решением является явное приведение параметра, я также хотел бы получить ввод для более элегантной версии constify - я удовлетворен, если он может обрабатывать только векторные итераторы и указатели, но он должен быть универсальным в базовом типе коллекции,

1 ответ

К сожалению, нет никакого способа добиться того, что вы пытаетесь сделать, кроме как со специализацией по шаблонам.

Для меня чистое решение будет:

//const version
template<class T>
void quux(const T it)
{ }

//non const version
template<class T>
void quux( T it)
{ }

На мой взгляд, это не дублирование кода, два интерфейса различны, и вы должны предлагать клиенту только тот минимум, который ему необходим.

Когда вы пытаетесь начать смешивать их, это может привести только к проблемам. Если вам нужно изменить параметр внутри функции, укажите только неконстантную версию, и клиент сможет без проблем вызвать вас с помощью константного параметра. (не const, чтобы const бросил работы из коробки)

В случае, если вы можете быть уверены, что не изменяете параметр внутри своей функции quux, просто предоставьте const.

Теперь const_casting параметра для его изменения внутри вашей функции - просто большой грех:-)

tldr; Подумайте о том, что предоставляет ваш интерфейс, и напишите минимум.

Другие вопросы по тегам