Універсальний конструктор Auto

З приходом C++11 з'явилася можливість оголошувати змінні типу auto, а компілятор сам визначив фактичний тип змінної, на основі типу инициализируемого значення. Це зручно, коли ми хочемо ініціалізувати змінну тип якої дуже складний, або невідомий, або він нам не дуже важливий, або просто для простоти.

Наприклад:

auto f = [](){}; //вказівник на функцію
auto r = foo(10); //тип повертається функцією foo
for (auto i = 0; i < 10; i++){} 

… і т. д. тобто в лівій частині рівності у нас автоматичний тип auto, а в правій частині значення чітко визначеного типу. А тепер уявімо, що у нас все навпаки:

int a = auto(10);

Зліва у нас чітко описаний тип, а праворуч щось невідоме. Звичайно в даному прикладі немає сенсу викликати універсальний конструктор, коли можна було просто привласнити до змінної a значення 10:

int a = 10;

Або в крайньому випадку викликати його конструктор:

int a(10);

А якщо це аргумент функції, наприклад:

str::map<char, int> myMap;
myMap.insert(pair<char, int>('a', 10));

Метод insert шаблонного класу map очікує чітко зазначений тип, але нам доводиться писати «pair<char, int>» знову і знову при кожному виклику. Добре якщо наш простий тип, а якщо там шаблон на шаблоні і шаблоном поганяє? Тут нам допоможе автоматичний конструктор:

myMap.insert(auto('a', 10));

Функція, Конструктор або Оператор auto, не важливо що це, створить нам якийсь об'єкт, який підходить під опис вхідного параметра методу insert.

Але на жаль в мові C++ поки немає такої методики створення об'єктів, але я сподіваюся, що коли-небудь вона з'явиться, а поки хочу представити свій варіант реалізації такого завдання. Звичайно ж основна мета спростити написання коду, але не менш важливе завдання не нашкодити нашій програмі: вона не повинна збільшиться в об'ємі, сповільнитися у виконанні і т. п. В ідеалі вона повинна бути ідентична тій, що якщо б ми писали без конструктора auto.

І так. Нам потрібно створити якийсь універсальний об'єкт, який би міг перетворитися на найпопулярніший тип і зробити це на етапі компіляції. Звичайно, я не беру до уваги оптимізацію компіляції O0, Og і т. п. візьмемо оптимізацію Os.

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

Для початку нам знадобиться універсальна мінлива, здатна зберігати як копії об'єктів, так і вказівники та посилання. Хоча покажчик та копія в даному випадку одне і теж, а от з посиланням складніше:

template < typename T>
struct Value {
constexpr Value(T v): v(v) {}
constexpr T get() {return v;}
T v;
};
template < typename T>
struct Value<T&> {
constexpr Value(T& v): v(&v) {}
constexpr T& get() {return *v;}
T* v;
};

Тут все відносно просто, беремо будь-який аргумент і зберігаємо його копію, а у випадку з посиланням перетворимо її в вказівник і назад.

Сама спеціалізація необхідна, щоб відсіяти аргументи за посиланням, щоб створити універсальність, а от без перетворення в вказівник компілятор відмовляється виконувати на етапі компіляції, хоча, здавалося б, у чому різниця, ми зберігаємо в покажчику або на засланні.

Тепер нам потрібно створити універсальний контейнер з необмеженим числом аргументів довільних типів:

template < typename... Types>
struct Container {
constexpr Container() {}
template < typename T> constexpr operator T() {return get<T>();}
template < typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);}
};
template < typename Type, typename... Types>
struct Container<Type, Types...> {
constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {}
template < typename T> constexpr operator T() {return get<T>();}
template < typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());}
Value<Type> arg;
Container<Types...> args;
};

Рекурсивний контейнер бере необмежену кількість аргументів різних типів і поміщає перший аргумент до себе, а решта на вкладений контейнер з рештою аргументами, поки не дійдемо до останнього аргументу.

Також цей контейнер має оператор перетворення до будь-якого потрібного типу викликаючи рекурсивний метод get з вкладенням всіх наявних аргументів.

Всі аргументи передаються в якості rvalue аргументів до самого кінцевого одержувача Value, щоб не втратити посилання.

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

template < typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}

Наостанок перемістимо клас Value в private область класу Container і вийде наступне:

template < typename... Types>
struct Container {
constexpr Container() {}
template < typename T> constexpr operator T() {return get<T>();}
template < typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);}
};

template < typename Type, typename... Types>
struct Container<Type, Types...> {
constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {}
template < typename T> constexpr operator T() {return get<T>();}
template < typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());}
private:
template < typename T>
struct Value {
constexpr Value(T v): v(v) {}
constexpr T get() {return v;}
T v;
};
template < typename T>
struct Value<T&> {
constexpr Value(T& v): v(&v) {}
constexpr T& get() {return *v;}
T* v;
};
Value<Type> arg;
Container<Types...> args;
};

template < typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}

Всі перетворення виконується на етапі компіляції і ні чим не відрізняється від прямого виклику конструкторів.

Правда є невеликі незручності — жоден суфлер не зможе вам запропонувати варіанти вхідних аргументів.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.