Situation
I have some existing functions which expect containers which can be iterated and which have certain value_type. The value_type can be a value or some (smart) pointer.
All the value_type have traits implementations for their base types, f.e.
template <>
struct Trait<A> {
static void test(A const& v) {
std::cout << "A " << v.i << std::endl;
}
};
The existing functions look like this:
template <typename T>
void foreach(T&& c) {
for (auto it = c.begin(); it != c.end(); ++it)
{
auto&& v = *it;
Trait<...>::test(it); // optional
Trait<...>::test(v);
}
}
At the moment there's a copy/paste of each trait per type and for all possible pointer and non pointer types.
Since there are many possible pointer and non pointer types i am looking for a way to get rid of the duplicates.
Possible Solutions
I came up with two approaches to instantiate the traits and dereference the value_type automatically.
The first uses a wrapper trait which dereferences on demand and forwards to the actual implementation. The second adds a template function to each function in the trait where the dereferencing is done. I haven't thought of a third approach yet.
Live demo of approach 1.
Live demo of approach 2.
Thoughts
I like approach 1 more because it seems easier for users and doesn't contaminate the actual traits with the dereferencing wrappers from approach 2 but i'm wondering if it's really worth the effort or if i should just copy/paste each trait for all possible pointer and non pointer types, e.g.
template <>
struct Trait<A> {
static void test(A const& v) {
std::cout << "A " << v.i << std::endl;
}
};
template <>
struct Trait<A*> {
static void test(A const* v) {
std::cout << "A " << v->i << std::endl;
}
};
template <>
struct Trait<std::unique_ptr<A>> {
static void test(std::unique_ptr<A> const& v) {
std::cout << "A " << v->i << std::endl;
}
};
template <>
struct Trait<std::shared_ptr<A>> {
static void test(std::shared_ptr<A> const& v) {
std::cout << "A " << v->i << std::endl;
}
};
// ...
Besides the additional template wrapper function in the traits i also don't like approach 2 because instantiating the trait either needs a manual decltype invocation or the workaround with a value instance of the trait type:
GetTrait<decltype(v)>::test(v); // decltype is a bit ugly
getTrait(v).test(v); // the instance seems a bit ugly
I'm hoping for a better and easier approach than this two but couldn't think of one yet.
Details and Comparisons
The structure and helpers for both approaches are mostly identical:
Concepts and methods to determine if dereferencing is needed and to actually do it:
template<typename T>
concept Dereferenceable = requires (T x) { *x; };
template<typename T>
concept NonDereferenceable = !Dereferenceable<T>;
template <typename T>
requires Dereferenceable<T> || NonDereferenceable<T>
struct Dereferenced
{};
template <Dereferenceable T>
struct Dereferenced<T>
{
using type = typename Dereferenced < std::remove_reference_t<decltype(*std::remove_reference_t<T>{}) >> ::type;
};
template <NonDereferenceable T>
struct Dereferenced<T>
{
using type = std::remove_reference_t<T>;
};
template <NonDereferenceable T>
auto&& dereferenceIfNeeded(T&& v) {
return v;
}
template <Dereferenceable T>
auto&& dereferenceIfNeeded(T&& v) {
return dereferenceIfNeeded(*v);
}
Test types:
struct A { int i; };
struct B { int j; };
Traits for approach 1:
namespace impl
{
template <typename T>
struct Trait {};
template <>
struct Trait<A> {
static void test(A const& v) {
std::cout << "A " << v.i << std::endl;
}
};
template <>
struct Trait<B> {
static void test(B const& v) {
std::cout << "B " << v.j << std::endl;
}
};
}
template <typename T>
using GetTrait = impl::Trait<std::remove_cvref_t<typename Dereferenced<T>::type>>;
template <typename T>
struct Trait {
static void test(T const& v) {
GetTrait<T>::test(dereferenceIfNeeded(v));
}
};
Traits for approach 2:
template <>
struct Trait<A> {
static void test(A const& v) {
std::cout << "A " << v.i << std::endl;
}
template <typename T>
static void test(T const& v) {
test(dereferenceIfNeeded(v));
}
};
template <>
struct Trait<B> {
static void test(B const& v) {
std::cout << "B " << v.j << std::endl;
}
template <typename T>
static void test(T const& v) {
test(dereferenceIfNeeded(v));
}
};
template <typename T>
using GetTrait = Trait<std::remove_cvref_t<typename Dereferenced<T>::type>>;
template <typename T>
auto getTrait(T&& t) {
return Trait<std::remove_cvref_t<typename Dereferenced<T>::type>>();
}
Testloop for approach 1:
template <typename T>
void foreach(T&& c) {
for (auto it = c.begin(); it != c.end(); ++it)
{
auto&& v = *it;
Trait<decltype(it)>::test(it);
Trait<decltype(v)>::test(v);
}
}
Testloop for approach 2:
template <typename T>
void foreach(T&& c) {
for (auto it = c.begin(); it != c.end(); ++it)
{
auto&& v = *it;
GetTrait<decltype(v)>::test(v); // decltype is a bit ugly
getTrait(v).test(v); // the instance seems a bit ugly
}
}
The testcases:
void test()
{
std::vector<A*> vrpa = { new A{2} };
std::vector<std::unique_ptr<A>> vupa; vupa.push_back(std::unique_ptr<A>{new A{ 3 }});
std::vector<std::shared_ptr<A>> vspa = { std::shared_ptr<A>{new A{4}} };
std::vector<A> va = { A{5} };
std::list<A*> lrpa = { new A{12} };
std::list<std::unique_ptr<A>> lupa; lupa.push_back(std::unique_ptr<A>{new A{ 13 }});
std::list<std::shared_ptr<A>> lspa = { std::shared_ptr<A>{new A{14}} };
std::list<A> la = { A{15} };
std::vector<B*> vrpb = { new B{112} };
std::vector<std::unique_ptr<B>> vupb; vupb.push_back(std::unique_ptr<B>{new B{ 113 }});
std::vector<std::shared_ptr<B>> vspb = { std::shared_ptr<B>{new B{114}} };
std::vector<B> vb = { B{115} };
std::list<B*> lrpb = { new B{1112} };
std::list<std::unique_ptr<B>> lupb; lupb.push_back(std::unique_ptr<B>{new B{ 1113 }});
std::list<std::shared_ptr<B>> lspb = { std::shared_ptr<B>{new B{1114}} };
std::list<B> lb = { B{1115} };
foreach(vrpa);
foreach(vupa);
foreach(vspa);
foreach(va);
foreach(lrpa);
foreach(lupa);
foreach(lspa);
foreach(la);
foreach(vrpb);
foreach(vupb);
foreach(vspb);
foreach(vb);
foreach(lrpb);
foreach(lupb);
foreach(lspb);
foreach(lb);
}
Solution
Live demo of the solution from Deduplicator.