I have a set of boost::shared_ptr which I want to be ordered and uniqued not by the shared pointers but by the strings. Do I have to provide a new comparison function getting shared pointers and comparing the contents or there is such a comparator already exists that I can use?
That's pretty specific, so you'll probably need a custom comparator.
This should work:
struct pointercompare
{
bool operator()(const boost::shared_ptr<std::string>& a, const boost::shared_ptr<std::string>& b)
{
return (*a)>(*b);
}
}
I would write a general way of wrapping predicates and iterators that maps value-semantics onto any pointer-like owner.
This then becomes completely general and re-usable.
Simple version here
Bonus code below introduces a complete library for this kind of thing
#include <utility>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <algorithm>
template<class Comp>
struct pointee
{
pointee(Comp comp = Comp()) : _comp(comp) {}
template<class APtr, class BPtr>
bool operator()(const APtr& a, const BPtr& b)
{
return _comp(*a, *b);
}
Comp _comp;
};
int main()
{
std::vector<boost::shared_ptr<int>> v;
std::sort(v.begin(), v.end(), pointee<std::less<>>());
std::sort(v.begin(), v.end(), pointee<std::greater<>>());
}
For bonus points...
#include <utility>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <algorithm>
#include <functional>
template<class T, class X, class Y>
struct is_binary_op
{
template<class U> static auto test(U* p) -> decltype((*p)(std::declval<X>(), std::declval<Y>()), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test((T*)0))::value;
};
template<class T, class X, class Y> static constexpr bool IsBinaryOp = is_binary_op<T, X, Y>::value;
template<class T, class X>
struct is_unary_op
{
template<class U> static auto test(U* p) -> decltype((*p)(std::declval<X>()), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test((T*)0))::value;
};
template<class T, class X> static constexpr bool IsUnaryOp = is_unary_op<T, X>::value;
namespace detail {
template<class Comp>
struct pointee
{
pointee(Comp comp = Comp()) : _comp(comp) {}
template<
class APtr,
class BPtr
>
auto operator()(const APtr& a, const BPtr& b) const
-> std::enable_if_t<
IsBinaryOp<Comp, decltype(*a), decltype(*b)>
, bool>
{
return _comp(*a, *b);
}
template<
class APtr
>
auto operator()(const APtr& a) const
-> std::enable_if_t<
IsUnaryOp<Comp, decltype(*a)>
, bool>
{
return _comp(*a);
}
Comp _comp;
};
template<class Iter>
struct deref_iter : Iter
{
deref_iter(Iter iter) : Iter(iter) {}
auto& operator*() const {
return **static_cast<const Iter&>(*this);
}
};
}
template<class Pred>
auto pointee(Pred&& pred)
{
return detail::pointee<std::decay_t<Pred>>(std::forward<Pred>(pred));
}
template<class Iter>
auto deref_pointee(Iter&& iter)
{
return detail::deref_iter<std::decay_t<Iter>>(std::forward<Iter>(iter));
}
int main()
{
std::vector<boost::shared_ptr<int>> v;
// sort using the less predicate on the pointee
std::sort(v.begin(), v.end(), pointee(std::less<>()));
// sort using the greater predicate on the pointee
std::sort(v.begin(), v.end(), pointee(std::greater<>()));
// apply a unary predicate to every pointee
std::for_each(v.begin(), v.end(), pointee(std::logical_not<>()));
// transform the pointees by binding a binary predicate to a value and
// turning it into a unary predicate that adds 6
std::transform(v.begin(), v.end(),
deref_pointee(v.begin()),
pointee(std::bind(std::plus<>(), 6, std::placeholders::_1)));
}
Related
Consider the following code:
#include <iostream>
#include <variant>
#include <memory>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
struct foo {
int f;
foo(int n) : f(n) {}
};
struct bar {
std::string b;
};
using unflattened_variant = std::variant<int, std::string, std::unique_ptr<foo>, std::unique_ptr<bar>>;
using flattened_variant = std::variant<int, std::string, foo, bar>;
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](int v) -> flattened_variant {
return v;
},
[](const std::string& s) -> flattened_variant {
return s;
},
[](const std::unique_ptr<foo>& f) -> flattened_variant {
return *f;
},
[](const std::unique_ptr<bar>& b) -> flattened_variant {
return *b;
},
},
v
);
}
int main()
{
unflattened_variant uv{ std::make_unique<foo>(42) };
auto fv = flatten(uv);
std::cout << std::get<foo>(fv).f << "\n";
}
This is a toy example that illustrates a situation I am running into in real code. I want to simplify the implementation of flatten(...) such that it is less verbose when there are more types in the variant.
Basically the situation is, I have a variant that contains some simple types and some move-only types that I would like to do something with. The operation that I need to perform is the same for all simple types and the same for all the move-only types; however, I can't think of a way of dealing with the two cases (simple or move-only) using only two visiting functions. e.g. this is illegal C++ but illustrates what I want to do
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](const std::unique_ptr<auto>& u_ptr) -> flattened_variant {
return *u_ptr;
},
[](auto simple_value) -> flattened_variant {
return simple_value;
},
},
v
);
}
I have dealt with situations like this in the past by using a custom variant cast, similar to the one implemented here, to cast to a variant containing just those types that need to be handled the same and then using a lambda taking an auto parameter as the visitor; however, such a cast would not work in this case because you can't copy unique_ptrs and you can't make a variant containing references. I suppose I could write a function that will cast to a variant of pointers but am wondering if there is an easier way.
template<template<class...>class, class> struct is_instance_of:std::false_type{};
template<template<class...>class Z, class...Ts> struct is_instance_of<Z,Z<Ts...>>:std::true_type{};
template<template<class...>class Z, class T>
constexpr bool is_instance_of_v=is_instance_of<Z,T>::value;
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
if constexpr (is_instance_of_v<std::unique_ptr, T>){
return *e;
else
return e;
}, v);
}
we add a trait to dispatch on, then use if constexpr to have 2 function bodies.
In c++20 we have lots more options.
[]<class T>(T const& e)->flattened_variant{
if constexpr (is_instance_of_v<std::unique_ptr, T>){
Then, going back to overloading solution, we have:
[]<class T>(std::unique_ptr<T> const&)
or
template<class T, template<class...>class Z>
concept instance_of=is_instance_of<Z,T>::value;
then
[](instance_of<std::unique_ptr> auto const& e)
or
[]<<instance_of<std::unique_ptr> T>(T const& e)
Prior to c++17 in c++14 we can use a dispatch helper:
template<class T0, class T1>
constexpr T0&& constexpr_branch( std::true_type, T0&& t0, T1&& ) { return std::forward<T0>(t0); }
template<class T0, class T1>
constexpr T1&& constexpr_branch( std::false_type, T0&&, T1&& t1 ) { return std::forward<T1>(t1); }
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
return constexpr_branch(
is_instance_of<std::unique_ptr, T>,
[](auto const& e){return *e;},
[](auto const& e){return e;}
)(e);
}, v);
}
going back to c++11 (where did you get your variant?), you could make an external class:
template<class R>
struct flatten {
template<class T>
R operator()(std::unique_ptr<T> const& p)const{
return *p;
}
template<class T>
R operator()(T const& v)const{
return v;
}
};
then just do a
return std::visit( flatten<flattened_variant>{}, v );
With reference to c++11 list initialization, may I initialize a list with an element and another list?
Let's say I have the following code:
#include <vector>
class Foo
{
public:
Foo(int value){m_v=value;}
private:
int m_v = 0;
};
int main()
{
std::vector<Foo> v1, v2, v3;
v1 = {Foo(1)}; //ok
v2 = {Foo(2), Foo(3)}; //ok
v3 = {Foo(3), v2}; //error: no match for ‘operator=’ (operand types are ‘std::vector’ and ‘’)
}
Is there a way to create in one line of code, using list initialization, a vector made of the element of another vector plus a new element (a prepend, in the example above).
We can create some template infrastructure to allow creation of vectors through optional concatenation of objects and other vectors.
This is very much a first cut:
#include <utility>
#include <vector>
namespace extended
{
template<class T>
struct appender
{
template<class V, class A, class Arg>
void operator()(std::vector<V, A>& vec, Arg&& arg) const
{
vec.push_back(std::forward<Arg>(arg));
}
};
template<class V2, class A2>
struct appender<std::vector<V2, A2>>
{
template<class V, class A, class X>
void operator()(std::vector<V, A>& vec, X&& arg) const
{
vec.insert(end(vec), begin(std::forward<X>(arg)), end(std::forward<X>(arg)));
}
};
template<class V, class A, class T>
auto append(std::vector<V, A>& target, T&& x) -> decltype(auto)
{
auto op = appender<std::decay_t<T>>();
op(target, std::forward<T>(x));
return target;
}
}
template<class T, class...Args>
auto make_vector(Args&&...args)
{
using extended::append;
std::vector<T> result;
using expand = int[];
expand {0,
(append(result, std::forward<Args>(args)), 0)...
};
return result;
}
class Foo
{
public:
Foo(int value){m_v=value;}
private:
int m_v = 0;
};
int main()
{
auto v1 = make_vector<Foo>(Foo(1)); //ok
auto v2 = make_vector<Foo>(Foo(2), Foo(3)); //ok
auto v3 = make_vector<Foo>(Foo(3), v2); //ok
}
Of course, by looking for common interfaces we can start to push the boundaries a little:
#include <utility>
#include <iterator>
#include <vector>
#include <list>
#include <set>
namespace extended
{
// The general case of an appender.
// simply calls emplace_back
template<class T, class Diff = void>
struct appender
{
template<class V, class A, class Arg>
void operator()(std::vector<V, A>& vec, Arg&& arg) const
{
vec.emplace_back(std::forward<Arg>(arg));
}
};
// specific specialisation for an appender where the
// source object supports begin() and end() (i.e. a container)
//
template<class T>
struct appender
<
T,
decltype(
std::begin(std::declval<T>()),
std::end(std::declval<T>()),
void()
)
>
{
template<class V, class A, class X>
void operator()(std::vector<V, A>& vec, X&& arg) const
{
vec.insert(std::end(vec), std::begin(std::forward<X>(arg)), std::end(std::forward<X>(arg)));
}
};
template<class V, class A, class T>
auto append(std::vector<V, A>& target, T&& x) -> decltype(auto)
{
auto op = appender<std::decay_t<T>>();
op(target, std::forward<T>(x));
return target;
}
}
template<class T, class...Args>
auto make_vector(Args&&...args)
{
using extended::append;
std::vector<T> result;
using expand = int[];
expand {0,
(append(result, std::forward<Args>(args)), 0)...
};
return result;
}
class Foo
{
public:
Foo(int value){m_v=value;}
bool operator<(const Foo& r) const { return m_v < r.m_v; }
private:
int m_v = 0;
};
int main()
{
auto v1 = make_vector<Foo>(Foo(1)); //ok
auto v2 = make_vector<Foo>(Foo(2), Foo(3)); //ok
auto v3 = make_vector<Foo>(Foo(3), v2); //ok
auto v4 = make_vector<Foo>(Foo(1),
std::list<Foo> { Foo(2), Foo(3) },
make_vector<Foo>(4, make_vector<Foo>(8, 9, 10)),
std::set<Foo> {Foo(6), Foo(7) }); // bizzare but ok
}
std::vector<Foo> means a std::vector of Foo instances. This means it cannot arbitrarily store other std::vector instances, which is what you're asking the compiler when writing
v3 = {Foo(3), v2};
std::initializer_list<T> is a homogeneous collection of T instances. std::vector<Foo>'s list constructor takes std::initializer_list<Foo>. There's no way to achieve what you want without manually unpacking v2's elements inside the curly braces.
Is there a way to create in one line of code, using list initialization, a vector made of the element of another vector plus a new element (a prepend, in the example above).
Using list initialization, no. You can write your own function to achieve the same thing, however.
Suppose I have this C++ function:
class C { ... };
void do(const vector<C>& cs) {
...
for (...) {
cs[i].do_whatever();
}
...
}
But C is expensive to copy so I might have something like this:
std::vector<C*> reorder_in_some_way(const std::vector<C>& cs) {
...
}
int main() {
std::vector<C> cs = ...;
std::vector<C*> reorderedCs = reorder_in_some_way(cs);
do(reorderedCs);
}
Obviously this won't work. I could get around it by giving up and just making do a template over any type like this:
template<typename T>
void do(const vector<T>& cs) {
But it really only works with C's and I'd like that to be encoded in the type system - and also it makes do() easier to understand if you don't have to go hunting around for places where it is used.
Is there any way to write do() so that it can generically take both vector<C> and vector<C*> (and for bonus points vector<reference_wrapper<C>>)?
Just write 2 template functions that applies a functor:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( i );
}
template<class T,typename Func>
void apply( const std::vector<T*> &v, Func f )
{
for( auto i : v ) f( *i );
}
then pass a lambda:
std::vector<C> vc;
std::vector<C*> vp;
auto call = []( const C &c ) { c.do_whatever(); };
apply( vc, call );
apply( vp, call );
(note you cannot call your function do - it is a keyword in C++)
live example
PS As you mentioned in comments your function apply is rather complex so you prefer to have only one copy of it, in this case create a helper:
template<class T>
const T &apply_helper( const T *t ) { return *t; }
template<class T>
typename std::enable_if<!std::is_pointer<T>::value, const T &>::type
apply_helper( const T &t ) { return t; }
then write your apply function only once:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( apply_helper( i ) );
}
live example N2
You might keep your do function generic, but specialize a getter for T& and T* that both return a T&:
namespace detail{
template<class T>
T& get(T& _in){
return _in;
}
template<class T>
T& get(T* _in){
return *_in;
}
} // namespace detail
template<class T>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
Demo
Either way you are going to need to specialize between pointers and references. I think that this pushes it to the smallest scope.
If you want to constrain do_a_thing to only accept C or C*, we can create a small trait to do this:
template <class T>
struct is_c : std::false_type{};
template <>
struct is_c<C>: std::true_type{};
template <>
struct is_c<C*>: std::true_type{};
And then modify do_a_thing with std::enable_if:
template<class T, std::enable_if_t<is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
For bonus points, we'll write another specialization of do_a_thing that gives a nice compiler error for types that do not satisfy the constraint:
template<class T>
struct always_false : std::false_type{};
template<class T, std::enable_if_t<!is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
static_assert(always_false<T>::value, "do_a_thing only works for C and C*");
}
Now the following will fail:
struct Q{};
std::vector<Q> qs;
do_a_thing(qs); // compiler error
Demo
Write a function template that gets a pair of iterators (not a vector).
Then pass it either normal vector<C>::iterators, or adapted vector<C*>::iterators, e.g. boost::transform_iterator instances.
Working example:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <boost/iterator/transform_iterator.hpp>
int& deref(int*& x) { return *x; }
template <class it>
void print(it from, it to)
{
std::copy(from, to, std::ostream_iterator<typename it::value_type>(std::cout, " "));
std::cout << "\n";
}
int main()
{
std::vector<int> a {4,3,7,1};
std::vector<int*> b {new int(2), new int(0), new int(11), new int(-3)};
// auto deref = [](int*& x) -> int& { return *x; };
// cannot use a lambda here because it's non-copyable
// and iterators must be copyable.
std::sort(std::begin(a), std::end(a));
std::sort(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
print(std::begin(a), std::end(a));
print(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
}
I think a possible solution could be to create a modified vector class that is generic with respect to pointerness, and can be implicitly converted to from a vector<T> or a vector<T*>. Like this:
template<typename T>
class VectorWrapper {
public:
VectorWrapper(const vector<T>& v) : reference(&v) { }
VectorWrapper(const vector<T*>& v) : pointer(&v) { }
const T& at(int idx) const {
if (reference)
return (*reference)[idx];
return *(*pointer)[idx];
}
// etc. for size() and so on. You could probably have
// this class derive from std::vector and reimplement its
// methods to switch between `reference` and `pointer`.
private:
const vector<T>* reference = nullptr;
const vector<T*>* pointer = nullptr;
};
void do_thing(VectorWrapper<C> wrapper) {
wrapper.at(0).whatever();
}
Not tested, and I don't think I'll go this route to be honest but it's the best I could come up with.
From http://en.cppreference.com/w/cpp/container/unordered_map/unordered_map, unordered_map can use lambda functions for hashing function. It is also answered in the following: How to use lambda function as hash function in unordered_map?
My question is about hashing a struct which includes a container, say a vector. Since cpprefence has the following code example of
#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_set>
#include <vector>
#include <unordered_map>
using std::hash;
using std::string;
using std::unordered_set;
using std::vector;
int main(int argc, char* argv[]) {
struct Goo {int val; };
auto hash = [](const Goo &g){ return std::hash<int>{}(g.val); };
auto comp = [](const Goo &l, const Goo &r){ return l.val == r.val; };
std::unordered_map<Goo, double, decltype(hash), decltype(comp)> m8(10, hash, comp);
return 0;
}
I have modified it so that it tries to use vector<int> in the Goo.
#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_set>
#include <vector>
#include <unordered_map>
using std::hash;
using std::string;
using std::unordered_set;
using std::vector;
int main() {
using vint = std::vector<int>;
struct Goo { vint v; };
auto hash = [](const Goo &g){
std::size_t hash_value = 0;
for (const int& i : g.v) {
hash_value ^= std::hash<int>{}(i);
}
return hash_value;
};
auto comp = [](const Goo &l, const Goo &r){
return unordered_set<int>(l.v.begin(), l.v.end()) ==
unordered_set<int>(r.v.begin(), r.v.end());
};
vint here;
std::unordered_map<Goo, double, decltype(hash), decltype(comp)>
m8(here,0, hash, comp);
return 0;
}
This code doesn't compile. The compiler complains about not no matching function for call to ‘std::unordered_map<main(int, char**)::Goo. The number of arguments seems to be not the issue, but something must be working not correctly. I would greatly appreciate your guidance.
I am using g++ -std=c++17 by the way.
I think that you didn't understand the example. This line:
std::unordered_map<Goo, double, decltype(hash), decltype(comp)> m8(10, hash, comp);
is responsible for creating unordered_map with at least 10 buckets and provided hash and comp functions. It does not create any unordered_map with 10 elements. Therefore, your code should look like this:
using vint = std::vector<int>;
struct Goo { vint v; };
auto hash = [](const Goo &g){
std::size_t hash_value = 0;
for (const int& i : g.v) {
hash_value ^= std::hash<int>{}(i);
}
return hash_value;
};
auto comp = [](const Goo &l, const Goo &r){
return std::unordered_set<int>(l.v.begin(), l.v.end()) ==
std::unordered_set<int>(r.v.begin(), r.v.end());
};
std::unordered_map<Goo, double, decltype(hash), decltype(comp)>
m8(10, hash, comp);
unordered_map simply does not have any constructor that will mach this:
std::unordered_map<Goo, double, decltype(hash), decltype(comp)>
m8(here, 0, hash, comp);
I suggest using the composable hashing infrastructure from N3980 Types Don't Know #. It also saves you from error-prone manual hash combining.
An example of hashing a structure with two members, one of which is a vector:
// Hashing infrastructure begin.
class fnv1a
{
std::size_t state_ = 14695981039346656037u;
public:
using result_type = std::size_t;
void operator()(void const* key, std::size_t len) noexcept {
unsigned char const* p = static_cast<unsigned char const*>(key);
unsigned char const* const e = p + len;
for (; p < e; ++p)
state_ = (state_ ^ *p) * 1099511628211u;
}
explicit operator result_type() noexcept {
return state_;
}
};
template<class HashAlgorithm>
struct uhash
{
using result_type = typename HashAlgorithm::result_type;
template <class T>
result_type operator()(T const& t) const noexcept {
HashAlgorithm h;
hash_append(h, t);
return static_cast<result_type>(h);
}
};
template<class HashAlgorithm, class T>
typename std::enable_if<std::is_integral<T>::value>::type
inline hash_append(HashAlgorithm& h, T const& t) noexcept {
h(&t, sizeof t);
}
template<class HashAlgorithm, class T, class... Args>
void hash_append(HashAlgorithm& h, std::vector<T, Args...> const& v) noexcept {
for(auto const& t : v)
hash_append(h, t);
hash_append(h, v.size());
}
template<class HashAlgorithm, class T, class... Args>
void hash_append(HashAlgorithm& h, std::unordered_map<T, Args...> const& v) noexcept {
for(auto const& t : v)
hash_append(h, t);
hash_append(h, v.size());
}
// Hashing infrastructure end
struct Goo
{
std::vector<int> a;
int b;
template<class HashAlgorithm>
friend void hash_append(HashAlgorithm& h, Goo const& v) noexcept {
hash_append(h, v.a);
hash_append(h, v.b);
}
bool operator==(Goo const& other) const {
return a == other.a && b == other.b;
}
};
int main() {
std::unordered_map<Goo, double, uhash<fnv1a>> m;
m[Goo{{1,2,3}, 1}] = 1;
}
Consider this working code. The function searchByDataMember uses a pointer to data member as argument to search a value among a container.
#include <iostream>
#include <list>
#include <string>
template <typename Container, typename T, typename DataPtr>
typename Container::value_type searchByDataMember (const Container& container, const T& t,
DataPtr ptr) {
for (const typename Container::value_type& x : container) {
if (x->*ptr == t)
return x;
}
return typename Container::value_type{};
}
struct Object {
int ID, value;
std::string name;
Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};
std::list<Object*> objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"),
new Object(9,12,"Rob"), new Object(2,11,"Tom"), new Object(15,16,"John") };
int main() {
const Object* object = searchByDataMember (objects, 11, &Object::value);
std::cout << object->name << '\n'; // Tom
}
So how to extend the above to using a variadic list of pointers to data members as arguments, in the event that a data member pointed to itself has data members to search? For example,
#include <iostream>
#include <list>
#include <string>
template <typename Container, typename T, typename... DataPtrs>
typename Container::value_type searchByDataMember (const Container& container, const T& t,
DataPtrs... ptrs) {
// What to put here???
}
struct Thing {
int ID, value;
std::string name;
Thing (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};
struct Object {
int rank;
Thing* thing;
Object (int r, Thing* t) : rank(r), thing(t) {}
};
std::list<Object*> objects { new Object(8, new Thing(5,6,"Sam")), new Object(2, new Thing(11,7,"Mark")),
new Object(1, new Thing(9,12,"Rob")), new Object(9, new Thing(2,11,"Tom"))};
int main() {
// The desired syntax.
// const Object* object = searchByDataMember (objects, 11, &Object::thing, &Thing::value);
// std::cout << object->thing->name << '\n'; // Tom (the desired output)
}
So here we wish to search among the container objects for the Object* that has a Thing* data member whose value data member is 11, which is the Object* that has "Tom". There is to be no limit on how big a chain of pointers to data members can be passed into searchByDataMember.
You need a way to apply operator ->* in succession:
template <typename T, typename MPtr>
auto arrow(T* obj, MPtr mptr)
{
return obj->*mptr;
}
template <typename T, typename MPtr, typename ... MPtrs>
auto arrow(T* obj, MPtr mptr, MPtrs... mptrs)
{
return arrow(obj->*mptr, mptrs...);
}
Then your search function is simple, something like: (I prefer to return iterator over value btw)
template <typename Container, typename T, typename... DataPtrs>
auto searchByDataMember (const Container& container, const T& t, DataPtrs... ptrs)
{
return std::find_if(std::begin(container), std::end(container),
[&](const auto&e) {
return arrow(e, ptrs...) == t;
});
}
Demo