Функція без шаблону, що обробляє будь-який контейнер елементів певного типу

Я хотів би мати функцію, описану в заголовку.

Я помітив, що алгоритми STL, які працюють з контейнерами будь-якого типу (список, вектор і т. Д.), Що містять елементи будь-якого типу (int, double), забезпечують загальну характеристику, використовуючи типи ітераторів як параметри шаблону, наприклад.

template
inline _OI
copy(_II __first, _II __last, _OI __result)

Це хороший метод, поки алгоритм не працює для будь-якого типу елементів. Єдиною вимогою для типу елемента є те, що він повинен мати конструктор копій.

Але припустимо, у нас є один конкретний тип

class MyElement
{
    public:
    void doSomethingWithElement();
};

і ми хочемо реалізувати функцію, яка обробляє кількість елементів цього типу, викликаючи функцію doSomethingWithElement() .

Написання функції, яка приймає контейнер певного типу, не дуже зручна, тому що багато контейнерів обробляються однаково (наприклад, ітераторами), і якщо буде потрібно для обробки контейнерів різних типів, ми будемо змушені дублювати кодекс. Написання шаблону відмінно працює, але це здається потворним, тому що ми повинні виконувати функцію на місці, де вона оголошена (у файлі заголовків). Крім того, коли ми хочемо обробляти елементи лише одного типу, параметризація цього типу не є правильним способом досягнення мети.

Я думав про інтерфейс ітератора, який можна використовувати як

void processContainer(IIterator begin, IIterator end);

Якщо цей ітератор мав чистий віртуальний оператор ++ та оператор *, які були реалізовані в похідних класах, ми могли передавати такі об'єкти в processContainer . Але є проблема: якщо Iterator є абстрактним класом, ми не можемо проаналізувати його при реалізації processContainer , і якщо ми передаємо покажчик на IIterator, ця функція зможе її змінити.

Хто-небудь знає будь-який інший рубати, щоб це зробити? Або інший підхід краще, ніж ці вище? Заздалегідь спасибі.

1
Використовуйте шаблони та припиніть шукати хакерів. Це просто так просто.
додано Автор Captain Obvlious, джерело
@rwong Повідомлення не стосується експортування функції або використання шаблонів з COM або взаємодії з ABI, так що саме ви маєте на увазі? Мені також спантеличено, чому ви вважаєте, що ці середовища забороняють використовувати шаблони.
додано Автор Captain Obvlious, джерело
@rwong Повідомлення не стосується експортування функції або використання шаблонів з COM або взаємодії з ABI, так що саме ви маєте на увазі? Мені також спантеличено, чому ви вважаєте, що ці середовища забороняють використовувати шаблони.
додано Автор Captain Obvlious, джерело
@CaptainObvlious Існує багато ситуацій, таких як експорт функції спільної бібліотеки стилів C, або Microsoft COM, або будь-який інший стабільний інтерфейс інтерфейсу ABI, який забороняє використання шаблонів.
додано Автор rwong, джерело
Загалом, коли мова йде про експорт на мови, які не є ідіоматичними C ++, пам'ятайте, що дуже мало інших мов має поняття діапазону, визначеного парою ітераторів. Замість цього більшість мов має "об'єкт переписувача" (не слід плутати з "енумами"), завданням якого є скасування поточного елемента, перехід на наступний пункт, скидання позиції та повернення підрахунку. Якщо ви робите адаптер для колекції з будь-якої причини, це більш конструктивний дизайн, щоб адаптер представляв колекцію або діапазон, а не два кінцеві точки.
додано Автор rwong, джерело
Загалом, коли мова йде про експорт на мови, які не є ідіоматичними C ++, пам'ятайте, що дуже мало інших мов має поняття діапазону, визначеного парою ітераторів. Замість цього більшість мов має "об'єкт переписувача" (не слід плутати з "енумами"), завданням якого є скасування поточного елемента, перехід на наступний пункт, скидання позиції та повернення підрахунку. Якщо ви робите адаптер для колекції з будь-якої причини, це більш конструктивний дизайн, щоб адаптер представляв колекцію або діапазон, а не два кінцеві точки.
додано Автор rwong, джерело

8 Відповіді

Простіший підхід полягає в тому, щоб ігнорувати обмеження та просто реалізувати вашу функцію як шаблон будь-якого ітератора. Якщо ітератор не посилається на тип, то користувач отримає жахливе повідомлення про помилку у рядках "тип X не має функції-члена doSomethingWithElement ".

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

Крім того, ви можете вирішити використовувати SFINAE для видалення перевантаження з набору кандидатів. Але, хоча SFINAE - прекрасний золотий молоток, я не впевнений, що у вас є відповідний цвях під рукою.

Якщо ви дійсно хочете йти далі по переулку, ви можете подивитися на any_iterator у бібліотеках Adobe як приклад того, як виконувати стирання типу на типі ітератора, щоб уникнути шаблону. Складність цього підходу на порядок вище, ніж будь-яка попередня, і витрати часу на виконання також будуть вищими, надаючи єдину перевагу більш чистому ABI та меншому розміру коду (різні ітератори можуть бути передані на одну функцію).

3
додано
@ Xeo: Дякуємо за інформацію, хоча я все ще думаю, що це шукає рішення, яке складніше, ніж вирішити проблему :)
додано Автор David Rodríguez - dribeas, джерело
Також є boost :: range_detail :: any_iterator , який використовується в boost :: any_range - але я дійсно не знаю, чому вони зробили це деталь. : /
додано Автор Xeo, джерело
Звичайно - але якщо ви хочете, щоб інтерфейс, який був обмежений для певних типів (або через std :: function , віртуальні інтерфейси або щось інше), що має зручний інструмент any_iterator < .
додано Автор Xeo, джерело

Простіший підхід полягає в тому, щоб ігнорувати обмеження та просто реалізувати вашу функцію як шаблон будь-якого ітератора. Якщо ітератор не посилається на тип, то користувач отримає жахливе повідомлення про помилку у рядках "тип X не має функції-члена doSomethingWithElement ".

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

Крім того, ви можете вирішити використовувати SFINAE для видалення перевантаження з набору кандидатів. Але, хоча SFINAE - прекрасний золотий молоток, я не впевнений, що у вас є відповідний цвях під рукою.

Якщо ви дійсно хочете йти далі по переулку, ви можете подивитися на any_iterator у бібліотеках Adobe як приклад того, як виконувати стирання типу на типі ітератора, щоб уникнути шаблону. Складність цього підходу на порядок вище, ніж будь-яка попередня, і витрати часу на виконання також будуть вищими, надаючи єдину перевагу більш чистому ABI та меншому розміру коду (різні ітератори можуть бути передані на одну функцію).

3
додано
@ Xeo: Дякуємо за інформацію, хоча я все ще думаю, що це шукає рішення, яке складніше, ніж вирішити проблему :)
додано Автор David Rodríguez - dribeas, джерело
Також є boost :: range_detail :: any_iterator , який використовується в boost :: any_range - але я дійсно не знаю, чому вони зробили це деталь. : /
додано Автор Xeo, джерело
Звичайно - але якщо ви хочете, щоб інтерфейс, який був обмежений для певних типів (або через std :: function , віртуальні інтерфейси або щось інше), що має зручний інструмент any_iterator < .
додано Автор Xeo, джерело

Ви не можете робити саме те, що хочете робити. Ви можете використовувати enable_if , щоб обмежити доступність функції:

template < typename Container >
typename enable_if,void>::type processContainer(Container c)...
0
додано

Ви не можете робити саме те, що хочете робити. Ви можете використовувати enable_if , щоб обмежити доступність функції:

template < typename Container >
typename enable_if,void>::type processContainer(Container c)...
0
додано

Неможливо створити абстрактний ітератор, який має повну ітераторну функціональність, включаючи можливість самостійно створювати копії, не змінюючи ітераторний інтерфейс. Тим не менш, ви можете реалізувати підмножину функцій ітератора за допомогою абстрактного базового класу:

#include 
#include 
#include 


template
struct AbstractIterator
{
    virtual bool operator!=(const AbstractIterator& other) const = 0;
    virtual void operator++() = 0;
    virtual T& operator*() = 0;
};

template
struct ConcreteIterator : AbstractIterator::value_type>
{
    typedef typename std::iterator_traits::value_type value_type;
    Iterator i;
    ConcreteIterator(Iterator i) : i(i)
    {
    }
    virtual bool operator!=(const AbstractIterator& other) const
    {
        return i != static_cast(&other)->i;
    }
    virtual void operator++()
    {
        ++i;
    }
    virtual value_type& operator*()
    {
        return *i;
    }
};

template
ConcreteIterator wrapIterator(Iterator i)
{
    return ConcreteIterator(i);
}


class MyElement
{
public:
    void doSomethingWithElement();
};

void processContainerImpl(AbstractIterator& first, AbstractIterator& last)
{
    for(; first != last; ++first)
    {
        (*first).doSomethingWithElement();
    }
}

template
void processContainer(Iterator first, Iterator last)
{
    ConcreteIterator wrapFirst = wrapIterator(first);
    ConcreteIterator wrapLast = wrapIterator(last);
    return processContainerImpl(wrapFirst, wrapLast);
}

int main()
{
    std::vector v;
    processContainer(v.begin(), v.end());

    std::list l;
    processContainer(l.begin(), l.end());
}
0
додано
Спасибі, це цікаве рішення, але воно включає шаблон processContainer знову.
додано Автор Oleg Andriyanov, джерело
Впровадження processContainer не є шаблоном - лише обгортка навколо нього. Обгортка існує лише для того, щоб зробити її безпосередньо викликати будь-яким типом ітератора.
додано Автор willj, джерело

Неможливо створити абстрактний ітератор, який має повну ітераторну функціональність, включаючи можливість самостійно створювати копії, не змінюючи ітераторний інтерфейс. Тим не менш, ви можете реалізувати підмножину функцій ітератора за допомогою абстрактного базового класу:

#include 
#include 
#include 


template
struct AbstractIterator
{
    virtual bool operator!=(const AbstractIterator& other) const = 0;
    virtual void operator++() = 0;
    virtual T& operator*() = 0;
};

template
struct ConcreteIterator : AbstractIterator::value_type>
{
    typedef typename std::iterator_traits::value_type value_type;
    Iterator i;
    ConcreteIterator(Iterator i) : i(i)
    {
    }
    virtual bool operator!=(const AbstractIterator& other) const
    {
        return i != static_cast(&other)->i;
    }
    virtual void operator++()
    {
        ++i;
    }
    virtual value_type& operator*()
    {
        return *i;
    }
};

template
ConcreteIterator wrapIterator(Iterator i)
{
    return ConcreteIterator(i);
}


class MyElement
{
public:
    void doSomethingWithElement();
};

void processContainerImpl(AbstractIterator& first, AbstractIterator& last)
{
    for(; first != last; ++first)
    {
        (*first).doSomethingWithElement();
    }
}

template
void processContainer(Iterator first, Iterator last)
{
    ConcreteIterator wrapFirst = wrapIterator(first);
    ConcreteIterator wrapLast = wrapIterator(last);
    return processContainerImpl(wrapFirst, wrapLast);
}

int main()
{
    std::vector v;
    processContainer(v.begin(), v.end());

    std::list l;
    processContainer(l.begin(), l.end());
}
0
додано
Спасибі, це цікаве рішення, але воно включає шаблон processContainer знову.
додано Автор Oleg Andriyanov, джерело
Впровадження processContainer не є шаблоном - лише обгортка навколо нього. Обгортка існує лише для того, щоб зробити її безпосередньо викликати будь-яким типом ітератора.
додано Автор willj, джерело

You could use std::for_each(): http://www.cplusplus.com/reference/algorithm/for_each/

повний код:

void callDoSomething(MyElement &elem)
{
    elem.doSomething();
}

int main()
{
  std::vector vec(100);
  std::for_each(vec.begin(), vec.end(), callDoSomething);
}
0
додано

You could use std::for_each(): http://www.cplusplus.com/reference/algorithm/for_each/

повний код:

void callDoSomething(MyElement &elem)
{
    elem.doSomething();
}

int main()
{
  std::vector vec(100);
  std::for_each(vec.begin(), vec.end(), callDoSomething);
}
0
додано
IT KPI C/С++ новым годом
IT KPI C/С++ новым годом
747 учасників

Чат обсуждения С/С++. - Вопросы "напишите за меня лабу" - это оффтоп. - Оффтоп, флуд, оскорбления и вбросы здесь не приняты. - За нарушение - предупреждение или mute на неделю. - За спам и рекламу - ban. Все чаты IT KPI: https://t.me/itkpi/1147