Design question (Templating)

Pages: 12
As described in this thread http://www.cplusplus.com/forum/general/143652/ I've realized a delegate factory
implementation for my IOC Container. For wrapping the required Delegates calls I've the following resover
template implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Define generic resolver template
template < class R> 
class GenericResolver;
	
class Resolver
{
protected:
    Resolver(){}
		
 public:
    virtual ~Resolver() = default;
		
    template<typename R, typename ... ARGS>
    std::shared_ptr<R> resolve(ARGS&& ... args) const
    {
		using derived_type = GenericResolver<R>;		
		auto rs = dynamic_cast< const derived_type& >(*this);
		return rs.template run<ARGS...>(std::forward<ARGS>(args)...);
    }		
};
	
template <typename T>
class GenericResolver : public Resolver
{
public:	
	GenericResolver(std::shared_ptr<brDelegate> delegate)
	:m_delegate(delegate){}
		
	GenericResolver(std::shared_ptr<T> instance)
	:m_instance(instance){}

	virtual ~GenericResolver() = default;
		
	template<typename ... ARGS>
	std::shared_ptr<T> run(ARGS&& ... args) const
	{
  	     if(m_instance.get()){
		return m_instance;
	     }
	     else{
		return std::make_shared<T>(
		   m_delegate->run<T, ARGS...>(std::forward<ARGS>(args)...));
	     } 
	}
		
	std::shared_ptr<brDelegate> getDelegate(void) const {
	     return m_delegate;
	}
		
private:
	std::shared_ptr<brDelegate> m_delegate = nullptr;
	std::shared_ptr<T> m_instance = nullptr;	
};


And here the call of the resolve from container:
1
2
3
4
5
6
template <typename T, typename ... ARGS>
inline std::shared_ptr<T> brIOCContainer::resolve(ARGS&& ... args) const
{	
	auto resolver = (iter->second)->getResolver();
	return resolver->resolve<T, ARGS...>(std::forward<ARGS>(args)...);			
}


This works fine and I could create delegates and resolve objects from IOC Container by request
the required type. Here is an example from my google-tests:
1
2
3
4
5
6
7
8
9
10
11
12
	delegate = brDelegate::create<Car>
	([] () -> Car { return Car("Mustang GT 500"); });	
	container.registerDelegate<Car>(delegate);
	
	auto r3 = container.resolve<Car>();
	ASSERT_NE(nullptr, r3);
	ASSERT_EQ("Mustang GT 500", r3->getName());
	
	auto r4 = container.resolve<Car>();
	ASSERT_NE(nullptr, r4);
	ASSERT_EQ("Mustang GT 500", r4->getName());
	ASSERT_NE(r3, r4);


It's also possible to register a delegate by contract, replacing only
the registration line:
1
2
3
4
	container.registerDelegateByContract<IVehicle, Car>(delegate);
	auto r3 = container.resolve<Car>();
	ASSERT_NE(nullptr, r3);
	ASSERT_EQ("Mustang GT 500", r3->getName());


At this point the resolve works, if I use the concrete type definition.
But I also want to resolve the concrete type Car if I use the IVehicle
interface request (This is required at each implementation point, where
I don't know the concrete implementation).

 
	auto r3 = container.resolve<IVehicle>();



But here the trouble starts. I could not work against an contract C,
while I need the concrete type T and ARGS to invoke the delegate call.

This call could not be compiled, while the pure virtual methods of the
interface IVehicle couldn't be resolved at this point:
 
	m_delegate->run<T, ARGS...>(std::forward<ARGS>(args)...));


On the other hand, I could not extend the template definition by using
a contract type, because it's not always given ...

Has anyone an idea how I could archive this? Or is this impossible?
I could be wrong, but I believe runtime type information is necessary for what you want to achieve.

Here's an idea:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <unordered_map>
#include <typeindex>
#include <functional>

using namespace std;

struct Container {

    template <class I, class C>
    void assoc() {
        generators[typeid(I)] = []() -> C * { return new C; };
    }

    template <class I>
    I * get() {
        auto g = generators[typeid(I)];
        return g ? static_cast<I *>(g()) : 0;
    }

    unordered_map<type_index, function<void *(void)>> generators;
};

struct IVehicle {
    virtual void sayHello() = 0;
};

struct Car : IVehicle {
    void sayHello() override { cout << "Hello, I am a Car!" << endl; }
};

struct Bicycle : IVehicle {
    void sayHello() override { cout << "Hello, I am a Bicycle!" << endl; }
};

void test(Container & c) {

    auto v = c.get<IVehicle>();

    if (v) {
        v->sayHello();
    } else {
        cout << "Couldn't find vehicle :(" << endl;
    }

}

int main() {

    Container c;

    test(c);

    c.assoc<IVehicle, Car>();
    test(c);

    c.assoc<IVehicle, Bicycle>();
    test(c);

}

http://ideone.com/nyIrwY
Last edited on
This is what I've done before. But I use factory delegates with return types and arbitary arguments, need to resolve at runtime. By this reason, your solution is not working for me.

However I do nearly the same on registration:
1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename C>
inline void brIOCContainer::registerByContract(void)
{		
	// create delegate for transient object creation based on concrete type
	auto delegate = brDelegate::create<T>([]() -> T { return T(); });
			
	/* Set up component of the concrete type T and register this component
	   using the contract type */
	auto component = std::make_shared<Component>(typeid(T));
	component->setResolver(std::make_shared<GenericResolver<T>>(delegate));		
	m_repository.insert(RegistryEntry_t(typeid(C), component));
}


For storage I using a std::multimap and nested component class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef std::multimap<std::type_index, std::shared_ptr<Component>> RegistryLookup_t;
typedef std::pair<std::type_index, std::shared_ptr<Component>> RegistryEntry_t;
RegistryLookup_t m_repository;

class Component
{
public:
	Component(std::type_index  type) : m_type(type){}
	~Component(){}

	std::type_index getType(void) const {
		return m_type;
	}
		
	std::shared_ptr<Resolver> getResolver(void){
		return m_resolver;
	}
		
	void setResolver(std::shared_ptr<Resolver> resolver){
		m_resolver = resolver;
	}
		
private:
	std::type_index m_type;
	std::shared_ptr<Resolver> m_resolver = nullptr;
};


The problem is that the resolve method of the resolver needs a type to cast the resolver to correct type. And a possible, different return type, when I use contracts.
I use factory delegates with return types and arbitary arguments

This is irrelevant to my point. I was just trying to keep things simple. The point was that you
have to maintain a dynamic mapping from interfaces to concrete types, which I now see you do,
which is a good thing. However, I think there are a couple of problems in the way you implement it.

For example, let's take a look at your registration function:

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename C>
inline void brIOCContainer::registerByContract(void)
{		
	// create delegate for transient object creation based on concrete type
	auto delegate = brDelegate::create<T>([]() -> T { return T(); });
			
	/* Set up component of the concrete type T and register this component
	   using the contract type */
	auto component = std::make_shared<Component>(typeid(T));
	component->setResolver(std::make_shared<GenericResolver<T>>(delegate));		
	m_repository.insert(RegistryEntry_t(typeid(C), component));
}

If T is the interface and C the concrete type, shouldn't it look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T, typename C>
inline void brIOCContainer::registerByContract(void)
{		
	// create delegate for transient object creation based on concrete type
	auto delegate = brDelegate::create<T *?>([]() -> C * { return new C; });
			
	/* Set up component of the concrete type T and register this component
	   using the contract type */
	auto component = std::make_shared<Component>(typeid(T));
	component->setResolver(std::make_shared<GenericResolver<T>>(delegate));		
	m_repository.insert(RegistryEntry_t(typeid(T), component)); // resolve using interface
	m_repository.insert(RegistryEntry_t(typeid(C), component)); // resolve using concrete type
}

?

[EDIT]

I was under the impression that T denotes the interface and C the concrete type judging by
your function call above: container.registerDelegateByContract<IVehicle, Car>(delegate);

If it's the other way around, i.e. T is the concrete type and C the interface (contract), then I believe adding m_repository.insert(RegistryEntry_t(typeid(T), component)); to your registration function and being
careful about the template parameter order when invoking it should be enough for this to work.

[/EDIT]

BTW, why is it necessary sto store the type_index in both the key and the value?
Also, why did you decide to use multimap?
Last edited on
Thanks for your hints. You are right I mixed concrete type T and cotract/interface C definitions. In the snipped above C is the interface, T the cocrete type.

At the delegate registration method I use this definition in the same way. However I changed the order. This is now fixed.

On the otherhand it seems to me that my delegate definition is wrong. There I've mixed C and T. New C makes no sense ... I have to fix it and try it out tomorrow.

Why I should use the concrete Type T at registration, when I have to search for Contract Type as key?

I use a multimap to allow storage of multiple concrete types using same contract. I.e. also Truck and Motorbike under IVehicle. And in future I will also support naming, to store multiple objects of same type by contract.
Glad to be of help.

Why I should use the concrete Type T at registration, when I have to search for Contract Type as key?

Your calls above suggest that you want to use both concrete types and interfaces as keys:

1
2
3
auto r3 = container.resolve<Car>();
/* ... */
auto r3 = container.resolve<IVehicle>();

But I noticed now that you use a different registration function for concrete types, so you can forget what I said.

I use a multimap to allow storage of multiple concrete types using same contract. I.e. also Truck and Motorbike under IVehicle. And in future I will also support naming, to store multiple objects of same type by contract.

Sounds nice.

Just make sure to know the alternatives and choose the best for your case (e.g. for naming support
a (unordered)map<Interface, <(unordered)map<string, ConcreteType>> looks more convenient to me):

http://stackoverflow.com/q/4437862/2327880
I checked this out. My implementation of the delegate was correct, you could see it in my 2nd post. I also store the values correctly. I use the interface as key and the cocrete type T for delegate definiton.

I try to visualize the problem. Because I've only access to this forum from mobile yet:

1. Register type Car by Contract IVehicle:
This results in a Key entry typeid (IVehicle)
and brDelegate<Car>. The GenericResolver
is also based on Car.

2. Try to Resolve IVehicle:
Found entry in map by IVehicle. The Component
returns the abstract Resolver and calls resolve
with IVehicle.

And this is my problem. At this point I only got the
interface type IVehicle but I neet the concrete type
Car.

This missmatch results in the compile failure,
while the run call of the GenericResolver forwards
the wrong Type:

 
   m_delegate<IVehicle, void>();
Last edited on
I get the general idea, but it's difficult to understand where exactly the error occurs without having a complete, 'compilable' snippet that demonstrates the problem. When you find the time, please provide such a code snippet.

At the moment, since the error seems related to casting between shared_ptrs of Car and IVehicle,
I suggest looking at this: http://www.cplusplus.com/reference/memory/dynamic_pointer_cast/
Last edited on
I've reduced the code to minimum and create a live example: http://ideone.com/goFTgT

The actual code is running. You have only to activate the out-commented resolve of the
car to force the compiler failure.
The first problem I see is this:

1
2
template < typename R, typename... Args > 
using brGenericDelegateType = std::function< R(Args...) > ;

What if R is IVehicle? This function is expected to return an instance of an abstract class, which is impossible. Maybe you meant to write:

1
2
template < typename R, typename... Args > 
using brGenericDelegateType = std::function< std::shared_ptr<R>(Args...) > ;

?
Last edited on
closed account (10X9216C)
1
2
// T = IVehicle
return resolver->run<T, ARGS...>(std::forward<ARGS>(args)...);

1
2
3
4
5
6
7
8
9
template<typename R, typename ... ARGS>
std::shared_ptr<R> run(ARGS&& ... args) const
{
    // R = IVehicle

    using derived_type = GenericResolver<R>;    
    auto rs = dynamic_cast< const derived_type& >(*this);
    return rs.template run<ARGS...>(std::forward<ARGS>(args)...); // this calls GenericResolver<IVehicle>::run(), which tries to construct an abstract object
}


You are calling std::make_shared<IVehicle>(). You have a lot of redundant code that does nothing. Even though you are using this it doesn't do anything, cause the function doesn't access anything in it. It might as well be static.

I would scrape what you have and start over. Seems you are confusing some concepts associated with virtual functions and templates.
Last edited on
Okay, I somehow managed to get it to work. I added a couple shared_ptrs, changed some Ts to Cs and
threw in a dynamic_pointer_cast as well. I agree with spectral though that it can be less elaborate :P

EDIT: Actually, dynamic_pointer_cast is only necessary when downcasting, so it's not needed here.

http://ideone.com/xqU4DE

code is too big for one post - part 1 is here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include <iostream>
using namespace std;

#include <assert.h> 
#include <exception>
#include <map>
#include <memory>
#include <typeindex>
#include <unordered_map>

// Predefine template delegate factory
template < typename R, typename... Args > 
class brGenericDelegate ;

// C++11 template alias to announce functor definition
template < typename R, typename... Args > 
using brGenericDelegateType = std::function< std::shared_ptr<R>(Args...) > ;

class brDelegate
{
protected:
	brDelegate(){}
	
public:

    virtual ~brDelegate() = default ;

    template < typename R, typename... Args >
    static std::shared_ptr<brDelegate> create( typename brGenericDelegate<R,Args...>::functor func ) 
	{ 
		return std::make_shared<brGenericDelegate<R,Args...>>(func) ; 
	}

    template < typename R, typename... Args > std::shared_ptr<R> run( Args... args ) const
    {
        using derived_type = brGenericDelegate<R,Args...> ;
        return dynamic_cast< const derived_type& >(*this)(args...) ;
    }
};

template < typename R, typename... Args > 
class brGenericDelegate : public brDelegate
{
public:
    using functor = brGenericDelegateType< R, Args... >;
    brGenericDelegate( functor f ) : fn(f) {}

    std::shared_ptr<R> operator() ( Args... args ) const { return fn(args...) ; }

private:
    const functor fn ;
};


class brIOCContainer 
{ 	
private:	
    // Define generic resolver template
    template < class R> 
    class GenericResolver;
			
	class Resolver
	{
	protected:
		Resolver(){}
		
	public:
		/** @brief Destructor */
		virtual ~Resolver() = default;
		
		template<typename R, typename ... ARGS>
		std::shared_ptr<R> run(ARGS&& ... args) const
		{
			using derived_type = GenericResolver<R>;		
			auto rs = dynamic_cast< const derived_type& >(*this);
			return rs.template run<ARGS...>(std::forward<ARGS>(args)...);
		}		
	};
	
	template <typename T>
	class GenericResolver : public Resolver
	{
	public:	
		GenericResolver(std::shared_ptr<brDelegate> delegate)
		:m_delegate(delegate){}
		
		virtual ~GenericResolver() = default;
		
		template<typename ... ARGS>
		std::shared_ptr<T> run(ARGS&& ... args) const
		{
			return //std::make_shared<T>(
				m_delegate->run<T, ARGS...>(std::forward<ARGS>(args)...);
				//);
		}
		
		std::shared_ptr<brDelegate> getDelegate(void) const {
			return m_delegate;
		}
		
	private:
		std::shared_ptr<brDelegate> m_delegate = nullptr;
	};
	
	class Component
	{
	public:
		Component(std::type_index  type) : m_type(type){}
		~Component(){}

		std::type_index getType(void) const {
			return m_type;
		}
		
		std::shared_ptr<Resolver> getResolver(void){
			return m_resolver;
		}
		
		void setResolver(std::shared_ptr<Resolver> resolver){
			m_resolver = resolver;
		}
		
	private:
		std::type_index m_type;
		std::shared_ptr<Resolver> m_resolver = nullptr;
	};
	
public:   
	typedef std::multimap<std::type_index, std::shared_ptr<Component>> RegistryLookup_t;
	typedef std::pair<std::type_index, std::shared_ptr<Component>> RegistryEntry_t;

	public:   
	brIOCContainer(){}
	brIOCContainer(const brIOCContainer& copy){
		m_repository = copy.m_repository;
	}
	virtual ~brIOCContainer(){}
   		
	template <typename T>
	void registerType(void);	
			
	template <typename T, typename C>
	void registerByContract(void);
	
	template <typename T, typename ... ARGS>
    std::shared_ptr<T> resolve(ARGS&& ... args) const;
 	
	template <typename T>
	bool contains(void) const;
	
	template <typename T, typename C>
	bool contains(void) const;
			
private:
	template <typename T>
	void validate(void);
	
	RegistryLookup_t::const_iterator find(std::type_index type) const{
		auto iter = m_repository.begin();
		while(iter!=m_repository.end()){			
			if((iter->first) == type ||
			   (iter->second)->getType() == type){
				break;
			}
			iter++;
		}
		return iter;
	}
						
	/** Registered elements */
	RegistryLookup_t m_repository;
};
Last edited on
... and part 2 is here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
template <typename T>
inline void brIOCContainer::registerType(void)
{
	// check if type has been already registered 
	auto iter = this->find(typeid(T));
	if(iter!=m_repository.end()){
		std::string msg = "[brIOCContainer]:registerType: A type: <";
		msg.append(typeid(T).name());		
		msg.append("> has been already registered!");
		throw std::runtime_error(msg.c_str());
	}
	
	// create delegate for transient object creation
	auto delegate = brDelegate::create<T>([]() -> std::shared_ptr<T> { return std::make_shared<T>(); });
	
	// create component and register resolver using defined delegate
	auto component = std::make_shared<Component>(typeid(T));	
	component->setResolver(std::make_shared<GenericResolver<T>>(delegate));	
	m_repository.insert(RegistryEntry_t(typeid(T), component));
}

template <typename T, typename C>
inline void brIOCContainer::registerByContract(void)
{
    // check if contract and type definitions are compatible
	assert((std::is_base_of<C,T>::value));

	// check if type is already registered by contract
	if((this->contains<T,C>())){
		std::string msg = "[brIOCContainer]:registerByContract: An unnamed type: <";
		msg.append(typeid(T).name());		
		msg.append("> has been already registered by contract!");
		throw std::runtime_error(msg.c_str());			
	}
		
	// create delegate for transient object creation based on concrete type
	auto delegate = brDelegate::create<C>([]() -> std::shared_ptr<C> { 
		//return dynamic_pointer_cast<C>(std::make_shared<T>()); 
		return std::make_shared<T>();
	});
			
	/* Set up component of the concrete type T and register this component
	   using the contract type */
	auto component = std::make_shared<Component>(typeid(C));
	component->setResolver(std::make_shared<GenericResolver<C>>(delegate));		
	m_repository.insert(RegistryEntry_t(typeid(C), component));
}

template <typename T, typename ... ARGS>
inline std::shared_ptr<T> brIOCContainer::resolve(ARGS&& ... args) const
{	
	std::type_index type = typeid(T);
	/*if(std::is_abstract<T>::value){
		std::string msg = "[brIOCContainer]:resolve: Cant resolve object of abstract type: ";
		msg.append(type.name());		
		throw std::runtime_error(msg.c_str());
	}*/
	
	auto iter = this->find(type);
	if(iter==m_repository.end()){
		std::string msg = "[brIOCContainer]:resolve: No entry found for requested class type: ";
		msg.append(type.name());		
		throw std::runtime_error(msg.c_str());
	}
	
	auto resolver = (iter->second)->getResolver();
	return resolver->run<T, ARGS...>(std::forward<ARGS>(args)...);			
}

template <typename T>
inline bool brIOCContainer::contains(void) const
{
	std::type_index type = typeid(T);	
    auto iter = m_repository.find(type);	
	return iter!=m_repository.end() ? true : false;
}

template <typename T, typename C>
inline bool brIOCContainer::contains(void) const
{
	bool result = false;
	std::type_index type = typeid(T);
	
	for(auto range = m_repository.equal_range(typeid(C));
	    range.first != range.second; ++range.first){

		if(type == (range.first->second)->getType()){
			result = true;
			break;
		}
	}	
	return result;
}

class IVehicle
{
public:
	IVehicle(){}
	virtual ~IVehicle() = default;

	virtual const std::string& getBrand(void) const = 0;
};

class Car : public IVehicle
{
public:
	Car():IVehicle(){}
	Car(const std::string& name):IVehicle(), m_name(name){}
	
	const std::string& getBrand(void) const override{
		return m_name;
	}
	
private:
	std::string m_name = "Mustang GT 500";
};

class Truck : public IVehicle
{
public:
	Truck():IVehicle(){}
	Truck(const std::string& name):IVehicle(), m_name(name){}
	
	const std::string& getBrand(void) const override{
		return m_name;
	}
	
private:
	std::string m_name = "MAN TGX EfficientLine 2 in long-haul transport";
};


int main() {
	
	brIOCContainer container;
	container.registerType<Truck>();
	
	auto truck = container.resolve<Truck>();
	std::cout << "Got a truck: " << truck->getBrand() << std::endl;
	
	container.registerType<Car>();
	
	// This resolve is working
	auto car1 = container.resolve<Car>();
	std::cout << "Got a vehicle: " << car1->getBrand() << std::endl;
	
	container.registerByContract<Car, IVehicle>();
	
	// This resolve occurs in a compiler failure
	auto car2 = container.resolve<IVehicle>();
	std::cout << "Got a vehicle: " << car2->getBrand() << std::endl;
	
	return 0;
}
Last edited on
closed account (10X9216C)
Jesus christ man, use pastebin or something. This site has really bad code support for being a site revolving around programming.
Jesus christ man, use pastebin or something. This site has really bad code support for being a site revolving around programming.

I did use ideone, but apparently you were too hasty to troll that you didn't notice. I believe source code relevant to the questions / answers here should be posted here too, so that it's guaranteed to exist as long as this site does. Are you claiming that e.g. the people who manage stackoverflow are stupid for not allowing links to source code without also posting the code? What if you found an answer to a problem and clicked on the code link only to realize that it's broken? Trust me, no amount of praying to Jesus Christ would help you get back that piece of code ;)
Last edited on
@m4ster r0shi, may I ask why you have "using namespace std" but then explicitly state it's part of the std namespace? :P Maybe you accidentally left it from the template on ideone.
Oh, I didn't notice that at all. I just cloned the OPs code and kept going from there. Since std:: was already
scattered all over the place, I kept using it. Perhaps this is a question the OP is more suitable to answer, then.
closed account (10X9216C)
"Troll"? The only troll is you, pasting 300 lines of code into this thread in two posts (the fact you needed two posts, reaching the character limit, is any indication to not continue with your current course of action?). Posting a link to the ideone would be enough (as hellhound did) so that you don't pollute the forum making the page giant. I'm not religious, i was using it as an expression. Stackoverflow has better support for code, it doesn't let you post that much code without adding a scroll bar. You only changed like 2 lines as well, you could have only posted the relevant changes if you care that deeply about a solution, that tbh is really specific and i doubt anyone else would benefit from it. Jesus saves.
Last edited on
I think complaining about 2 posts of code isn't a big deal. If it was a full page or a bunch of them without code tags that would be different. Anyways, this is a bit off topic from the OP. PS most people post a code snippet then a link to ideone or coliru. Though I often times don't anymore since there is now a "gear" button that takes you to cpp.sh for the code (obviously won't work with large amounts of code).
Sorry, spectral, no more feeding from me here. Go play somewhere else.
Pages: 12