Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Главная миссия именованных параметров — исключить ошибки при вызове функций и повысить читабельность кода — осуществляется.Если сужать миссию именованных параметров до такой — то выполняется. Но мне кажется что опциональность параметров — очень важная возможность. И её пользовательскими типами не решить. Иначе придётся либо писать функции со множеством параметров, либо всё равно городить костыли описанные по ссылкам в начале вашей статьи.
CreateOptions(2, 3, Default, 8);
std::unordered_set<std::string>
после подстановки параметров по умолчанию получается:std::unordered_set<std::string, std::hash<std::string>, std::equal_to<std::string>,
std::allocator<std::string>>
std::unordered_set<std::string, std::hash<std::string>, std::equal_to<std::string>,
my_allocator<std::string>>
вместо какой-нибудь такой записи:std::unordered_set<std::string, Allocator : my_allocator<std::string>>
void foo(int *ptr = NULL, int int_arg = 5);
int glob_int = 5;
foo(); // ОК эквивалентно foo(NULL,5)
foo(&glob_int); // OK, эквивалентно foo(&glob_int, 5)
foo(13); // BAD - мы попытались привести int к указателю, вместо вызова foo(NULL, 13)
Скажем, да, в Луа можно присваивать значения сразу нескольким переменнымДа и в плюсах обычно можно:
x = y = getPosition();
Нельзя задавать одно начальное значение для нескольких переменных при конструировании.Да, менять местами параметры такой подход не позволит, но это не так и важно.
struct width {
explicit width(int val) : v{val} {}
int v;
};
struct height {
explicit height(int val) : v{val} {}
int v;
};
template <typename... T> int getVolume(T... args) {
auto t = make_tuple(args...);
int w = get_element_by_type<width>(t).v;
int h = get_element_by_type<height>(t).v;
return w * h * h;
}
int _tmain(int argc, _TCHAR *argv[]) {
cout << getVolume(height(2), width(1)) << endl;
cout << getVolume(width(1), height(2)) << endl;
return 0;
}
namespace detail {
template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl {
static constexpr auto value = N;
};
template <class T, std::size_t N, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, T, Args...> {
static constexpr auto value = N;
};
template <class T, std::size_t N, class U, class... Args>
struct get_number_of_element_from_tuple_by_type_impl<T, N, U, Args...> {
static constexpr auto value =
get_number_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
};
} // namespace detail
template <class T, class... Args>
T get_element_by_type(const std::tuple<Args...> &t) {
return std::get<detail::get_number_of_element_from_tuple_by_type_impl<
T, 0, Args...>::value>(t);
}
#include "stdafx.h"
#include <iostream>
using namespace std;
namespace detail {
template <typename T, typename H, typename... Args>
typename enable_if<!is_same<T, H>::value, T>::type
get_by_type_impl(H head, Args... tail) { return get_by_type_impl<T>(tail...); }
template <typename T, typename... Args>
T get_by_type_impl(T head, Args... tail) { return head; }
template <typename T, typename... Args> T get_by_type_impl() { return T(); }
} // namespace detail
template <typename T, typename... Args> T get_arg(Args... tail) {
return detail::get_by_type_impl<T>(tail...);
}
template <typename T> struct arg {
explicit arg(T val) : v{val} {}
T v;
operator T() { return v; }
};
struct width : arg<int> {
using arg::arg;
};
struct height : arg<int> {
explicit height(int i = 42) : arg(i) {}
};
template <typename... T> int getVolume(T... args) {
int w = get_arg<width>(args...);
int h = get_arg<height>(args...);
return w * h * h;
}
int _tmain(int argc, _TCHAR *argv[]) {
cout << getVolume(width(10)) << endl;
cout << getVolume(height(20), width(1)) << endl;
cout << getVolume(width(1), height(2)) << endl;
return 0;
}
class Date
{
public:
void set(const Day& d);
void set(const Month& m);
void set(const Year& y);
};
template<class D, class M, class Y>
Date createDate(const D& d, const M& m, const Y& y)
{
Date date;
date.set(d);
date.set(m);
date.set(y);
return date;
}
//теперь можно вызывать
createDate(Day(3), Year(4), Month(55));
createDate(Year(4), Month(55), Day(3));
createDate(Month(55), Day(3), Year(4));
Day, Year и Month, а достаточно, чтобы можно было однозначно конвертировать каждый аргумент в один из этих типов. Надо бы только защититься от подобных вызовов:createDate(Day(3), Day(3), Day(3));
#include <type_traits>
template<class D, class M, class Y>
Date createDate(const D& d, const M& m, const Y& y)
{
static_assert(!std::is_same<D, M>::value, "types are equal");
static_assert(!std::is_same<D, Y>::value, "types are equal");
static_assert(!std::is_same<M, Y>::value, "types are equal");
Date date;
date.set(d);
date.set(m);
date.set(y);
return date;
}
Day:struct T {
operator Day() const;
};
createDate(T{}, Year(4), Month(55));
и это реально здорово и является преимуществом вашего метода перед предложенным выше.createDate(T{}, Day(4), Month(55));
поэтому до конца проблема не решена.Т.е. не разрастётся ли скомпилированный код от этих наворотов.Нет, компилятор соптимизирует весь этот код до печати двух четвёрок, можете в этом сами убедиться, посмотрев ассемблерный код (я использовал
printf() для печати, чтобы листинг был покороче и попонятней). Хотя теоретически в этом коде куча дополнительных вызовов мелких вспомогательных функций (можете поставить опцию "-O0" вместо "-O2" и сами посмотреть), весь дизайн С++ построен на том, что компилятор сумеет такие вызовы заинлайнить и соптимизировать получившийся код.Второй вопрос — обёртывание базового типа int в структуру с методом добавит нам указатели на методы или нет?Нет, указатели добавляют только виртуальные методы, эти структуры в точности совпадают с сишной структурой с одним int'ом внутри.
typedef struct Date {
int day;
int month;
int year;
} Date;
void f(Date d);
...
f((Date){.day = 1, .month = 2, .year = 3});
Date date = Date.getBuilder().setDay(3).setMonth("Jan").setYear(1999).build();someMethod(user1, user2);
…
Если же объекты неравнозначны, например, метод ставит в подчинение user2 объекту user1, то совсем нелишними будут специальные типы, отражающие роли объектов.
Date theDate = createDate(2, 3, 4);
Date theDate = createDate(currentYear, currentMonth, currentDay);
Date theDate = createDate(Year(currentYear), Month(currentMonth), Day(currentDay));
cLabel* Label = InitWindow(cLabel,panel);
Label->setLeft(0);
Label->setTop(0);
Label->setWidth(panel->width());
Label->setHeight(panel->height());
Label->setVAlign(VA_CENTER);
Label->setHAlign(HA_CENTER);
Label->setCaption(Text);
Label->setColor(cColorf::WHITE);
Label->setFont(«Roboto-Regular.ttf»,fontSize);
есть некий наследуемый метод объявленный традиционно:
virtual void classA::createDate(Year year, Month month, Day Day);
и наследники его во всю наследуют:
virtual void classB::createDate(Year year, Month month, Day Day);
Приходит тим лид, и в классе родители параметры меняются местами:
virtual void classA::createDate(Month month, Day Day, Year year);
а в наследниках мы изменить забыли:
virtual void classB::createDate(Year year, Month month, Day Day);
final ещё и оптимизировать помогает, скажем, в таком коде:struct A { virtual void f(); }
struct B : public A { virtual void f() override final; }
struct C final : public A { virtual void f() override; }
void do_with_B(B& b) { b.f(); }
void do_with_C(C& c) { c.f(); }
в обоих функциях do_with... вызов f() будет вызвана напрямую соответствующая функция, тогда как без final там был бы вызов через таблицу виртуальных функций.Date theDate = createDate(Year(currentYear), Month(currentMonth), Day(currentDay));
enum class apples : unsigned int {};
apples будет отдельным типом (в него нельзя будет присвоить int или груши, скажем), при этом по умолчанию для него будут определены все операторы сравнения и функция хеширования (поэтому, например, его можно положить в любой STL контейнер, ничего дополнительно не дописывая). Из минусов: не работают арифметические операции, не работает вывод через потоки (и то, и то можно ручками дописать) плюс проблемы с синтаксисом конструирования, т.к. единственный способ сконструировать такой тип из встроенного числового типа — это сделать явный каст:apples a(0); // Compilation error
apples b( apples(0) ); // OK
apples c = apples{0}; // Compilation error
apples d = apples(0); // OK
Но есть некоторые товарищи,
достаточно найти парочку основных сущностей
/**внутренний глобальный идентификатор клиента*/
#ifdef _DEBUG
BOOST_STRONG_TYPEDEF(DWORD, client_handle_type);
BOOST_CLASS_IMPLEMENTATION(client_handle_type, primitive_type)
namespace boost
{
template <>
struct is_integral<client_handle_type> : public ::boost::true_type {};
template <>
struct is_unsigned<client_handle_type> : public ::boost::true_type {};
template <>
struct is_signed<client_handle_type> : public ::boost::false_type {};
template <>
struct is_arithmetic<client_handle_type> : public ::boost::true_type {};
template <>
inline ::std::string lexical_cast<::std::string, client_handle_type>(const client_handle_type & arg)
{
return lexical_cast<::std::string, DWORD>((DWORD)arg);
}
}
#else
typedef DWORD client_handle_type;
#endif
Date theDate = createDate(2, 3, 4);
const int kDay = 2;
const int kYear = 3;
const int kMonth = 4;
Date theDate = createDate(kDay, kYear, kMonth);
Имя — это совсем не произвольная строка. Хотя бы потому, что выбирается из заранее известного множества. Еще оно, скажем, не содержит цифр (хотя тут я не уверен).
Pascals operator/ (Newtons force, Squaremeters square) {return Pascals(force.m_value/square.m_value)}[<Measure>] type m
[<Measure>] type s
let distance = 100<m>
let time = 5<s>
let speed = distance / time // Размерность будет m/s
Самое время привести основную мысль статьи. Примитивным стандартным типам не место в интерфейсах реальной системы. Такие типы — просто строительные блоки, из которых нужно именно строить, а не пытаться жить внутри.
В том числе потому, что вероятность ошибки при написании 3 новых классовНам же не с нуля надо эти классы разрабатывать с какой-то уникальной функциональностью в каждом, они весьма однотипны. Всё можно устроить так, что добавление нового класса будет требовать от нас написание одной строчки кода (например, завернуть объявление класса в макрос) или одного клика в IDE для вставки соответствующего сниппета.
Именованные параметры C++. Не пригодились