- PVSM.RU - https://www.pvsm.ru -
Здесь я расскажу о работе с кортежами C++ (tuple [1]), приведу несколько полезных функций, которые в состоянии существенно облегчить жизнь при использовании кортежей, а также приведу примеры использования этих функций. Всё из личного опыта.
Перебрать все элементы кортежа, вызвав для каждого одну и ту же функцию — наверное, первая задача, встающая перед разработчиком при использовании кортежей. Реализация весьма незамысловата:
namespace tuple_utils
{
// вызвать 'callback' для каждого элемента кортежа
/*
struct callback
{
template<std::size_t, class T>
void operator()( T&& element )
{
// do something
}
};
tupleForeach( callback(), myTuple );
*/
template<class TCallback, class ...TParams>
void tupleForeach( TCallback& callback, const std::tuple<TParams...>& tuple );
namespace
{
template<std::size_t Index, class TCallback, class ...TParams>
struct _foreach_
{
static void tupleForeach_( TCallback& callback, const std::tuple<TParams...>& tuple )
{
// такой пересчёт необходим для выполнения callback'a над элементами в порядке их следования
const std::size_t idx = sizeof...( TParams ) - Index;
callback.operator()<idx>( std::get<idx>( tuple ) );
_foreach_<Index - 1, TCallback, TParams...>::tupleForeach_( callback, tuple );
}
};
template<class TCallback, class ...TParams>
struct _foreach_<0, TCallback, TParams...>
{
static void tupleForeach_( TCallback& /*callback*/, const std::tuple<TParams...>& /*tuple*/ ) {}
};
} //
template<class TCallback, class ...TParams>
void tupleForeach( TCallback& callback, const std::tuple<TParams...>& tuple )
{
_foreach_<sizeof...( TParams ), TCallback, TParams...>::tupleForeach_( callback, tuple );
}
} // tuple_utils
Здесь используется вспомогательная структура _foreach_, имеющая в качестве дополнительного template-параметра очередной индекс кортежа. Единственный её статический метод tupleForeach_ вызывает для элемента с этим индексом заданную через callback функцию, после чего вызывается рекурсивно. Частичная специализация данной структуры для индекса, равного нулю, вырождена и является завершением рекурсии.
struct ForeachCallback
{
template<std::size_t Index, class T>
void operator()( T&& element )
{
std::cout << "( " << Index << " : " << element << " ) ";
}
};
void foo()
{
auto myTyple = std::make_tuple( 42, 3.14, "boo" );
tuple_utils::tupleForeach( ForeachCallback(), myTyple );
}
// определим тип getter'а как константный метод без параметров
template<class TResult, class TOwner>
using TGetter = TResult( TOwner::* )() const;
// класс, хранящий getter'ы одного объекта
template<class TGetterOwner, class ...TParams>
class MyGetterContainer
{
// определим тип getter'а для объекта заданного класса
template<class TResult>
using TMyGetter = TGetter<TResult, TGetterOwner>;
.....
private:
.....
// проверить, нет ли среди getter'ов вырожденных (значения nullptr)
void checkGetters();
// кортеж getter'ов разных типов (т.е. возвращающих значения разных типов)
std::tuple<TMyGetter<TParams>...> m_getters;
};
namespace
{
// callback, выполняющий проверку каждого getter'а
template<class TGetterOwner>
struct GetterCheckCallback
{
// непосредственно функция проверки, которая будет вызвана для каждого getter'а
// здемь мы не используем 'Index' и действуем одинаково для всех элементов
template<std::size_t Index, class T>
void operator()( const TGetter<T, TGetterOwner>& element )
{
assert( element != nullptr );
}
};
} //
template<class TGetterOwner, class ...TParams>
void MyGetterContainer<TGetterOwner, TParams...>::checkGetters()
{
// вызываем callback для проверки всех getter'ов
tuple_utils::tupleForeach( GetterCheckCallback<TGetterOwner>(), m_getters );
}
Ещё одна приходящая в голову задача для удобства работы с кортежами — функция, строящая новый кортеж последовательно из результатов выполнения заданной функции над элементами данного кортежа. Весьма распространённая в функциональных языках задача. Её реализация, пожалуй, ещё проще:
namespace tuple_utils
{
// сформировать новый кортеж (кортеж другого типа) из результатов вызова 'callback'а для каждого из элементов кортежа 'sourceTuple'
/*
struct callback
{
template<std::size_t, class R, class T>
R operator()( T&& element )
{
// do something
}
};
mapTuple( callback(), myTuple );
*/
template<class TCallback, class TSourceTuple>
auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple );
namespace
{
template<class TCallback, class TSourceTuple, std::size_t... Indices>
auto mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple, std::index_sequence<Indices...> )
{
return std::make_tuple( callback.operator()<Indices>( std::get<Indices>( sourceTuple ) )... );
}
} //
template<class TCallback, class TSourceTuple>
auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple )
{
return mapTuple_( callback, sourceTuple, std::make_index_sequence<std::tuple_size<TSourceTuple>::value>() );
}
} // tuple_utils
Здесь используется вспомогательная функция mapTuple_, принимающая дополнительный параметр, набор всех индексов кортежа, через index_sequence [2]. Из результатов выполнения заданной через callback функции над элементами по каждому из индексов формируется результирующий кортеж.
struct MapCallback
{
template<std::size_t Index, class T>
std::string operator()( T&& element )
{
std::stringstream ss;
ss << "( " << Index << " : " << element << " )";
std::string result;
result << ss;
return result;
}
};
void foo()
{
auto sourceTyple = std::make_tuple( 42, 3.14, "boo" );
auto strTuple = tuple_utils::mapTuple( MapCallback(), sourceTyple );
}
// определим тип getter'а как константный метод без параметров
template<class TResult, class TOwner>
using TGetter = TResult( TOwner::* )() const;
// класс, хранящий getter'ы одного объекта
template<class TGetterOwner, class ...TParams>
class MyGetterContainer
{
// определим тип getter'а для объекта заданного класса
template<class TResult>
using TMyGetter = TGetter<TResult, TGetterOwner>;
.....
protected:
.....
// получить значения всех getter'ов
std::tuple<TParams...> getterValues() const;
private:
.....
// сам объект, у которого будут вызываться getter'ы
TGetterOwner& m_getterOwner;
// кортеж getter'ов разных типов (т.е. возвращающих значения разных типов)
std::tuple<TMyGetter<TParams>...> m_getters;
};
namespace
{
// callback, возвращающий значения getter'ов
template<class TGetterOwner>
struct GetterValuesCallback
{
public:
// конструктор
GetterValuer( const TGetterOwner& getterOwner ) :
m_getterOwner( getterOwner )
{
}
// непосредственно функция, возвращающая значение getter'а; она будет вызвана для каждого getter'а
// здемь мы не используем 'Index' и действуем одинаково для всех элементов
template<std::size_t Index, class T>
T operator()( const TGetter<T, TGetterOwner>& oneGetter )
{
return ( m_getterOwner.*oneGetter )();
}
private:
const TGetterOwner& m_getterOwner;
};
} //
template<class TGetterOwner, class ...TParams>
std::tuple<TParams...> MyGetterContainer<TGetterOwner, TParams...>::getterValues() const
{
// вызываем callback для формирования нового кортежа из возвращённых значений всех getter'ов
return tuple_utils::mapTuple( GetterValuesCallback<TGetterOwner>( m_getterOwner ), m_getters );
}
Что ещё хотелось бы «уметь делать» с кортежами — это использовать их содержимое как параметры вызова какой-либо функции (естественно, порядок и тип аргументов которой соответствует порядку и типу элементов кортежа). Реализация данной функции весьма схожа с реализацией функции map:
namespace tuple_utils
{
// вызвать 'callback', принимающий в качестве параметров распакованный кортеж 'tuple'
/*
struct callback
{
template<class TResult, class ...TParams>
TResult operator()( TParams... )
{
// do something
}
};
callTuple( callback(), myTuple );
*/
template<class TCallback, class ...TParams>
auto callTuple( TCallback& callback, const std::tuple<TParams...>& tuple );
namespace
{
template<class TCallback, class TTuple, std::size_t... Indices>
auto callTuple_( TCallback& callback, const TTuple& tuple, std::index_sequence<Indices...> )
{
return callback( std::get<Indices>( tuple )... );
}
} //
template<class TCallback, class ...TParams>
auto callTuple( TCallback& callback, const std::tuple<TParams...>& tuple )
{
return callTuple_( callback, tuple, std::index_sequence_for<TParams...>() );
}
} // tuple_utils
Здесь, как и в случае с map, используется вспомогательная функция callTuple_, принимающая дополнительный параметр, набор всех индексов кортежа, через index_sequence [2]. Она вызывает заданную через callback функцию, передавая ей все элементы кортежа, соответствующие индексам. Результатом её выполнения является результат выполнения переданной функции.
bool checkSomething( int a, float b, const std::string& txt );
struct CallCallback
{
template<class TResult, class ...TParams>
TResult operator()( TParams... params )
{
return checkSomething( params... );
}
};
void foo()
{
std::tuple<int, float, std::string> paramsTyple = std::make_tuple( 42, 3.14, "boo" );
bool isParamsValid = tuple_utils::callTuple( CallCallback(), paramsTyple );
}
// класс, хранящий getter'ы одного объекта
template<class TGetterOwner, class ...TParams>
class MyGetterContainer
{
.....
protected:
.....
// получить значения всех getter'ов
std::tuple<TParams...> getterValues() const;
.....
};
// определим тип setter'а как неконстантный void-метод c параметрами
template<class TOwner, class ...TParams>
using TSetter = void( TOwner::* )( TParams... );
// класс, вызывающий setter одного объекта со значениями getter'ов другого
template<class TSetterOwner, class TGetterOwner, class ...TParams>
class MySetterCaller : public MyGetterContainer<TGetterOwner, TParams...>
{
// определим тип getter'а для объекта заданного класса с заданными параметрами
using TMySetter = TSetter<TSetterOwner, TParams...>;
.....
public:
.....
// вызвать setter со значениями getter'ов
void callSetter();
private:
.....
// сам объект, у которого будет вызываться setter
TSetterOwner& m_setterOwner;
// непосредственно setter
TMySetter m_setter;
};
namespace
{
// callback, выполняющий вызов setter'а
template<class TSetterOwner, class ...TParams>
struct CallSetterCallback
{
public:
// конструктор
GetterPasser( TSetterOwner& setterOwner, TSetter<TSetterOwner, TParams...> setter ) :
m_setterOwner( setterOwner ), m_setter( setter )
{
}
// непосредственно функция, выполняющая вызов setter'а
void operator()( TParams... params )
{
return ( m_setterOwner.*m_setter )( params... );
}
private:
TSetterOwner& m_setterOwner;
TSetter<TSetterOwner, TParams...> m_setter;
};
} //
template<class TSetterOwner, class TGetterOwner, class ...TParams>
void MySetterCaller<TSetterOwner, TGetterOwner, TParams...>::callSetter()
{
// получим кортеж значений от getter'ов
std::tuple<TParams...> _getterValues = getterValues();
// вызываем callback для вызова setter'а от полученных значений
tuple_utils::callTuple( CallSetterCallback( m_setterOwner, m_setter ), _getterValues );
}
P.S. В С++17 будет доступен std::apply [3], который выполняет тот же функционал.
// определим тип обработчика-метода как void-метод с одним параметром
template<class TObject, class TValue>
using TMethodHandler = void( TObject::* )( const TValue& );
// класс, хранящий обработчики-функторы
template<class ...TValues>
class MyHandlerContainer
{
public:
// конструктор; принимает переменное число разнотипных обработчиков-функторов
MyHandlerContainer( const std::function<void( const TValues& )>... handlers );
.....
// статический метод создания экземрляра класса из обработчиков-методов
template<class TMethodOwner>
static MyHandlerContainer<TValues...>* createFrom( TMethodOwner& methodOwner, TMethodHandler<TMethodOwner, TValues>... handlers );
.....
};
namespace
{
// callback для проверки валидности обработчиков-методов
template<class TMethodOwner>
struct CheckCallback
{
// конструктор
CheckCallback() :
IsValid( true )
{
}
// функция проверки каждого из обработчиков-методов
template<std::size_t Index, class TValue>
void operator()( const TMethodHandler<TMethodOwner, TValue>& oneMethodHandler )
{
if( oneMethodHandler == nullptr )
IsValid = false;
}
bool IsValid;
}
// callback для создания набора обработчиков-функторов из набора обработчиков-методов
template<class TMethodOwner>
struct FunctorHandlerCallback
{
public:
// конструктор
FunctorHandlerCallback( TMethodOwner& methodOwner ) :
m_methodOwner( methodOwner )
{
}
// функция создания обработчика-функтора из обработчика-метода
template<std::size_t Index, class TValue>
std::function<void( const TValue& )> operator()( const TMethodHandler<TMethodOwner, TValue>& oneHandlers )
{
return [ this, oneHandlers ]( const TValue& tValue ) { ( m_methodOwner.*oneHandlers )( tValue ); };
}
private:
TMethodOwner& m_methodOwner;
};
// callback для создания экземпляра класса 'MyHandlerContainer' из набора обработчиков-методов
template<class ...TValues>
struct CreateCallback
{
// функция создания экземпляра класса 'MyHandlerContainer' из набора обработчиков-методов
auto operator()( std::function<void( const TValues& )>... handlers )
{
return new MyHandlerContainer<TValues...>( handlers... );
}
};
} //
template<class ...TValues>
template<class TMethodOwner>
MyHandlerContainer<TValues...>* MyHandlerContainer<TValues...>::createFrom( TMethodOwner& methodOwner, TMethodHandler<TMethodOwner, TValues>... handlers )
{
// кортеж обработчиков-методов
auto methodsTuple = std::make_tuple( handlers... );
// проверим, все ли методы валидны
CheckCallback checkCallback;
tuple_utils::tupleForeach( checkCallback, methodsTuple );
// если все методы валидны
if( checkCallback.IsValid )
{
// (нужно, чтобы он не удалился при выходе из функции)
FunctorHandlerCallback<TMethodOwner>* functorHandlerCallback = new FunctorHandlerCallback<TMethodOwner>( methodHolder );
// кортеж обработчиков-функторов
auto handlersTuple = tuple_utils::mapTuple( *functorHandlerCallback, methodsTuple );
// создание из кортеж обработчиков-функторов экземпляра класса 'MyHandlerContainer'
MyHandlerContainer<TValues...>* result = tuple_utils::callTuple( CreateCallback<TValues...>( multiProperty ), handlersTuple );
return result;
}
// если не все методы валидны
assert( false );
return nullptr;
}
index_sequence [2] появляется только в С++14. Если хочется использовать данные функции в С++11 (в котором и появился tuple), либо по каким-то иным причинам не хочется использовать index_sequence [2], либо просто интересно посмотреть на реализацию функций map и call без них, вот реализация:
namespace tuple_utils
{
// сформировать новый tuple (tuple другого типа) из результатов вызова 'callback'а для каждого элемента tuple'а
/*
struct callback
{
template<std::size_t, class R, class T>
R operator()( T&& element )
{
// do something
}
};
mapTuple( callback(), myTuple );
*/
template<class TCallback, class TSourceTuple>
auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple );
namespace
{
template<std::size_t Index, class TCallback, class TSourceTuple, std::size_t... Indices>
struct _map_
{
auto static mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple )
{
const std::size_t idx = std::tuple_size<TSourceTuple>::value - Index;
return _map_<Index - 1, TCallback, TSourceTuple, Indices..., idx>::mapTuple_( callback, sourceTuple );
}
};
template<class TCallback, class TSourceTuple, std::size_t... Indices>
struct _map_<0, TCallback, TSourceTuple, Indices...>
{
auto static mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple )
{
return std::make_tuple( callback.operator()<Indices>( std::get<Indices>( sourceTuple ) )... );
}
};
} //
template<class TCallback, class TSourceTuple>
auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple )
{
return _map_<std::tuple_size<TSourceTuple>::value, TCallback, TSourceTuple>::mapTuple_( callback, sourceTuple );
}
} // tuple_utils
namespace tuple_utils
{
// вызвать 'callback', принимающий в качестве параметров распакованный tuple
/*
struct callback
{
template<class TResult, class ...TParams>
TResult operator()( TParams... params )
{
// do something
}
};
callTuple( callback(), myTuple );
*/
template<class TCallback, class TResult, class ...TParams>
TResult callTuple( TCallback& callback, const std::tuple<TParams...>& tuple );
namespace
{
template<std::size_t Index, class TCallback, class TResult, class TTuple, class ...TParams>
struct _call_
{
static TResult callTuple_( TCallback& callback, const TTuple& tuple, TParams... params )
{
const std::size_t idx = std::tuple_size<TTuple>::value - Index;
return _call_<Index - 1, TCallback, TResult, TTuple, TParams..., typename std::tuple_element<idx, TTuple>::type>::callTuple_( callback, tuple, params..., std::get<idx>( tuple ) );
}
};
template<class TCallback, class TResult, class TTuple, class ...TParams>
struct _call_<0, TCallback, TResult, TTuple, TParams...>
{
static TResult callTuple_( TCallback& callback, const TTuple& tuple, TParams... params )
{
return callback( params... );
}
};
} //
template<class TCallback, class TResult, class ...TParams>
TResult callTuple( TCallback& callback, const std::tuple<TParams...>& tuple )
{
return _call_<sizeof...( TParams ), TCallback, TResult, std::tuple<TParams...>>::callTuple_( callback, tuple );
}
} // tuple_utils
Подход к реализации данных функций одинаков: мы вручную «накапливаем» индексы (вместо index_sequence [2]) или параметры, а затем, в конце рекурсии, выполняем необходимые действия с уже полученным набором индексов/параметров. Хотя лично мне подход с индексами кажется более универсальным.
Спасибо, что уделили время!
Автор: AzrielFuzz
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/224146
Ссылки в тексте:
[1] tuple: http://ru.cppreference.com/w/cpp/utility/tuple
[2] index_sequence: http://en.cppreference.com/w/cpp/utility/integer_sequence
[3] std::apply: http://en.cppreference.com/w/cpp/utility/apply
[4] Источник: https://habrahabr.ru/post/318236/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.