What is the best way to deal with the fact that some types require members / methods to be accessed with the . operator whilst others with the -> operator.
Is it best to write the code for the . operator and have the caller wrap the type as show in the code sample below.
Coming from a C# background I am not use to having this particular issue.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
template<class T>
class container{
public:
void add(T element){
elements_.push_back(std::move(element));
}
void process(){
for(auto& a: elements_){
a.print();
}
}
private:
std::vector<T> elements_;
};
class printable{
public:
void print(){
std::cout << "Print\n";
}
};
template<class T>
class printable_forwarder{
public:
printable_forwarder(T element): element_{std::move(element)}{
}
void print(){
element_->print();
}
private:
T element_;
};
int main()
{
container<printable> c1;
c1.add(printable{});
c1.process();
container<printable_forwarder<std::shared_ptr<printable>>> c2;
std::shared_ptr<printable> sp{std::make_shared<printable>()};
c2.add(printable_forwarder<decltype(sp)>{sp});
c2.process();
}
Does this appear better?
#include <iostream>
#include <string>
#include <memory>
#include <type_traits>
#include <vector>
template<typename T>
class dereference
{
public:
inline static T& get(T& value){
return value;
}
};
template<typename T>
class dereference<T*>
{
public:
inline static typename std::add_lvalue_reference<typename std::remove_pointer<T>::type>::type get(T* value){
return *value;
}
};
template<typename T>
class dereference<std::shared_ptr<T>>
{
public:
inline static T& get(std::shared_ptr<T> value){
return *value.get();
}
};
template<class T>
class container{
public:
void add(T const& v){
items_.push_back(v);
}
void print_all(){
for(auto& a: items_){
dereference<T>::get(a).print();
}
}
private:
std::vector<T> items_;
};
struct printable{
void print(){
std::cout << "Printing\n";
}
};
int main()
{
container<printable> c1;
c1.add(printable{});
c1.print_all();
container<std::shared_ptr<printable>> c2;
c2.add( std::shared_ptr<printable>(new printable{}));
c2.print_all();
}
What is the best way to deal with the fact that some types require members / methods to be accessed with the . operator whilst others with the -> operator.
Just don't.
Your job is to write template<class T> class container
. That container holds T
s. If your users want to do something on the T
, you should expose the ability to do something - but it is their responsbility to perform that action properly. Otherwise, you're just adding a ton of code bloat. Great, you gave me a way to print all the elements, but what if I know what to call foo()
on them, or find the first element for which bar()
returns something bigger than 42? Clearly, you're not going to write for_each_foo()
and find_if_bar_is_42()
.
This is why the standard library separates containers from algorithms. The way to make your container as usable as possible is to have it expose two iterator
s via begin()
and end()
, and then I can just do whatever I need to do as the user:
container<T> values;
values.add(...);
// I know to use '.'
for (T& t : values) {
t.print();
}
container<T*> pointers;
pointers.add(...);
// I know to use '->'
for (T* t : pointers) {
t->print();
}
auto iter = std::find_if(pointers.begin(), pointers.end(), [](T* t){
return t->bar() == 42;
});
Barring that, you can add a bunch of member functions that themselves take callables, so you pass on the work to the user:
template <class F>
void for_each(F&& f) {
for (auto& elem : elements_) {
f(elem); // option a
std::invoke(f, elem); // option b, as of C++17
}
}
so the above examples would be:
values.for_each([](T& t){ t.print(); });
pointers.for_each([](T* t){ t->print(); });
values.for_each(std::mem_fn(&T::print));
pointers.for_each(std::mem_fn(&T::print));
Note that it's always up to the user to know what to do. Also, if you use std::invoke()
in the implementation of for_each
, then you could just write:
pointers.for_each(&T::print);
values.for_each(&T::print);
and, for that matter:
container<std::unique_ptr<T>> unique_ptrs;
unique_ptrs.for_each(&T::print);
As an alternative to parametrising container
with a printer type as suggested in another answer, I'd suggest parametrising the container::process()
method instead:
template<typename F>
void process(F&& func)
{
for (auto& e : elements)
{
func(e);
}
}
Then the client code would look like this:
container<printable> value_container;
value_container.add(...);
value_container.process([](printable& obj) { obj.print(); });
container<printable*> ptr_container;
ptr_container.add(...);
ptr_container.process([](printable* obj) { obj->print(); });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With