Re: Possible Tag Dispatching
On 16.01.2014 07:49, Ryan wrote:
On Wednesday, January 15, 2014 5:02:56 AM UTC-8, Alf P. Steinbach wrote:
On 15.01.2014 01:47, Ryan wrote:
I assume that means that an "A" object stores an object that can be
either a C, a D or an E.
It actually stores quite a few of them that were read in from a
configuration file.
Well, your original code does not support access to a collection of
objects in each A instance, just a single stored object.
However, unless the stored objects are all of the same type, or some
other very predictable pattern, then nothing much of real importance is
changed.
Instead of a single object of unknown type we're simply dealing with a
collection of such, and putting things in a collection is mostly *an
orthogonal issue*. The C++ standard library has a number of containers
that you can use depending on the collection requirements. E.g.,
std::vector, std::set, std::dequeue, std::map, etc.
* * *
That said, it sounds like you would be better off using boost::variant
or something like that, rather than re-inventing the wheel.
* * *
However, /that/ said, let's proceed to reinvent the wheel, since that's
much of what this group is about: creating things and understanding
things, not just being pointed to relevant cans of food to heat.
A getType function is not needed for that, nor is some type traits class
needed.
Since I don't know what types 'A' stores then I need something from 'A'
to tell me the type to retrieve.
As the example code I posted illustrated (by way of just working
nicely), you do not need such extra information when you can assume that
the types are polymorphic.
For example, with polymorphic types you can use type-specific values by
associating them with the type at hand, via the C++ built-in `typeid`.
However, for this discussion I'll assume the worst, namely that you're
dealing with non-polymorphic classes that you don't control.
I assume that you want the ability to call a general `add` function, and
have it routed to or applied with the stored object of unknown type.
True.
Great! :-)
Hopefully the following captures your requirements, but it's not clear
what aspects of the original code are requirements and what aspects are
simply artifacts of a particular attempt to implement the requirements.
After seeing your suggestion let me clarify a few items.
Structures B, C, D and E are all separate and can't be modified. They
don't share a common base class.
The 'add' methods are really part of a class that also can't be modified.
struct G {
void add(int num, B b, C c) { std::cout << "Added C" << std::endl; }
void add(int num, B b, D d) { std::cout << "Added D" << std::endl; }
void add(int num, B b, E e) { std::cout << "Added E" << std::endl; }
};
The only class I have control over is the 'A' class.
a.stored<D>(); // Throws! As it should.
This is a problem. I don't know that I want a 'D' type. This is
specified from the 'A' class.
Such getters for the stored object was specified by your original code,
where you expressed a desire to have the type-specific getters replaced
by a single function template, such as the above.
a.stored<a.getType()>();
The a.getType() call /can not/, even in principle, provide compile time
information about the dynamically stored object. That would require a
time machine. Such a time machine is rumored to exist for Python
(Guido's secret[1]), but AFAIK there is no such for C++ (although Bjarne
has sometimes been suspected of having changed the past so that the only
remnant of 1984 is the "++" in "C++").
However, let's look at the original code again, now with the added
information that
(1) there can be more than one stored object,
(2) only class A is open for modification,
(3) the classes can not, apparently, be assumed to be polymorphic,
(4) the `add` functions are really non-static members of a class G, so
that an instance of class G is required for the calls, and
(5) a /manifest/ way to obtain type identification is required, not just
support that with e.g. application of typeid would yield such id.
#include <iostream>
enum eType { cc, dd, ee };
struct B {};
struct C {};
struct D {};
struct E {};
struct A {
eType getType(void) { return dd; }
C getC(void) { return C(); }
D getD(void) { return D(); }
E getE(void) { return E(); }
};
void add(int num, B b, C c) { std::cout << "Added C" << std::endl; }
void add(int num, B b, D d) { std::cout << "Added D" << std::endl; }
void add(int num, B b, E e) { std::cout << "Added E" << std::endl; }
int main() {
A a;
switch(a.getType()) {
case cc: add(3, B(), a.getC()); break;
case dd: add(4, B(), a.getD()); break;
case ee: add(5, B(), a.getE()); break;
default:
std::cout << "Problem" << std::endl;
break;
}
return 0;
}
[code]
#ifndef CPPX_IS_DELETED
# define CPPX_IS_DELETED = delete
#endif
#include <iostream>
#include <map>
#include <memory> // std::unique_ptr
#include <stdexcept> // std::exception
#include <typeindex> // std::type_index
#include <type_traits> // std::decay
#include <utility> // std::move
#include <vector>
using namespace std;
struct B {};
struct C {};
struct D {};
struct E {};
struct G
{
void add( int n, B, C ) { cout << "Added C+" << n << endl; }
void add( int n, B, D ) { cout << "Added D+" << n << endl; }
void add( int n, B, E ) { cout << "Added E+" << n << endl; }
};
namespace stored {
struct Object
{
virtual void add( int, B, G& ) = 0;
virtual auto type_id() const -> type_index = 0;
};
template< class C_D_or_E_ >
struct Stored_part_
: Object
, C_D_or_E_
{
void add( int n, B b, G& g ) override { g.add( n, b, *this ); }
auto type_id() const
-> type_index
override
{ return typeid( C_D_or_E_ ); }
Stored_part_( C_D_or_E_&& other ): C_D_or_E_( move( other ) ) {}
};
} // namespace stored
template< class Type >
auto type_id()
-> type_index
{ return typeid( Type ); }
auto const id_c = type_id<C>();
auto const id_d = type_id<D>();
auto const id_e = type_id<E>();
class Variant
{
private:
unique_ptr<stored::Object> stored_object_;
Variant( Variant const& ) CPPX_IS_DELETED;
Variant& operator=( Variant const& ) CPPX_IS_DELETED;
public:
auto stored_object()
-> stored::Object&
{ return *stored_object_; }
auto stored_object() const
-> stored::Object const&
{ return *stored_object_; }
template< class Stored >
auto stored()
-> Stored&
{ return dynamic_cast<Stored&>( *stored_object_ ); }
template< class Stored >
auto stored() const
-> Stored const&
{ return dynamic_cast<Stored&>( *stored_object_ ); }
auto type_id() const
-> type_index
{ return stored_object().type_id(); }
void add( int n, B b, G& g )
{ stored_object().add( n, b, g ); }
template< class C_D_or_E_ >
Variant( C_D_or_E_ o )
: stored_object_( new stored::Stored_part_<C_D_or_E_>( move( o ) ) )
{}
Variant( Variant&& other )
: stored_object_( move( other.stored_object_ ) )
{}
};
class A
{
private:
typedef vector<Variant> Objects;
Objects objects_;
public:
auto begin()
-> Objects::iterator
{ return objects_.begin(); }
auto end()
-> Objects::iterator
{ return objects_.end(); }
template< class Type >
A& include( Type o )
{ objects_.push_back( move( o ) ); return *this; }
};
auto main() -> int
{
G g;
A a;
a
.include( D() )
.include( E() )
.include( C() )
.include( C() );
map<type_index, int> numbers{ {id_c, 2}, {id_d, 3}, {id_e, 4} };
for( auto& var : a )
{
cout << "----------------------------------------------------" << endl;
auto const id = var.type_id();
int const number = numbers[id];
cout << "The type is " << id.name() << ", number is " << number << endl;
var.add( number, B(), g );
cout << "Attempting to access stored object as a `C`..." << endl;
try
{
var.stored<C>();
cout << "Worked nicely." << endl;
}
catch( exception const& x )
{
cerr << "! " << x.what() << endl;
}
cout << "Attempting to access stored object as a `D`..." << endl;
try
{
var.stored<D>(); // Throws! As it should.
cout << "Worked nicely." << endl;
}
catch( exception const& x )
{
cerr << "! " << x.what() << endl;
}
}
}
[/code]
But again, while this DIY approach can be instructive, if this is for
professional work then you're probably better served by using e.g.
boost::variant (or boost::any, but I can never remember which is which
of those, or even what the difference is), or, by /changing the design/.
For, the idea of a polymorphic collection is very Java-ish.
There may be far better ways to approach the real problem -- whatever
that real problem is -- for C++.
Cheers & hth.,
- Alf
Notes:
[1] <url: http://www.catb.org/jargon/html/G/Guido.html> "Mythically,
Guido's most important attribute besides Python itself is Guido's time
machine, a device he is reputed to possess because of the unnerving
frequency with which user requests for new features have been met with
the response ?I just implemented that last night...?."
PS:
Norwegian/Scandinavian readers: I'm looking for a job since I'm now
almost recovered from surgery over the last year and a half (just a
little bit more surgery now at the end of the month) . Pro hiring me:
I'm pretty good. Con: age. I'm a moderator of this group, and I was
awarded Microsoft MVP in 2012. Your firm just may be able to catch me.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]