Problems with std::shared_ptr in perfect forwarding

I got trouble again with perfect forwarding and std::shared_ptr.

Because it's hard to explain, I've created the following live
example: http://ideone.com/xC052N

I got a delegate factory based on an interface and concrete implementation, which returns me a std::shared_ptr of the interface. In some cases this returned pointer causes in a compile failure when I try to use it as argument in a perfect forwarding call:

1
2
3
4
// auto texture = std::make_shared<brGL3Texture>(brTexture::TYPE_TEXTURE_2D);

auto texture = delegate2->run<brTexture, brTexture::eTextureType>(brTexture::TYPE_TEXTURE_2D); 
auto test1 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>(texture);



prog.cpp: In function ‘int main()’:
prog.cpp:213:79: error: no matching function for call to ‘resolve(std::shared_ptr<brTexture>&)’
  auto test1 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>(texture);
                                                                               ^
prog.cpp:213:79: note: candidate is:
prog.cpp:92:20: note: template<class R, class ... ARGS> std::shared_ptr<_Tp1> resolve(ARGS&& ...)
 std::shared_ptr<R> resolve(ARGS&& ... args)
                    ^
prog.cpp:92:20: note:   template argument deduction/substitution failed:
prog.cpp:213:79: note:   cannot convert ‘texture’ (type ‘std::shared_ptr<brTexture>’) to type ‘std::shared_ptr<brTexture>&&’
  auto test1 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>(texture);


If I use the disabled std::make_shared call, no failure occurs. What I do not understand (and meanwhile drives me crazy) is, that I see no difference to the other test case (MachineGun). This works without any failure, and it's also based on an interface delegate ...

Any idea what is going wrong?
Yes. You should not provide template atguments for universal references.
auto test1 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>(texture);
http://stackoverflow.com/a/14473319/3410396
Thanks for the hint. However I've to use shared_ptr in my delegates as template arguments. What can I do? Remove perfect forwarding?

And why it is working if I use std::make shared for the pointer instead of the lambda expression? And why in some cases it works also with the lambda? It looks like an unexpected behaviour. In 3 of 4 google test cases it's working, in the fourth not ...
Last edited on
However I've to use shared_ptr in my delegates as template arguments.
And how would that interfere with not providing template argument manually and letting compiler to deduct type?

And why in some cases it works also with the lambda?
Depends on code. Cannot say for sure.

Manually setting argument to the non-reference type contradicts "universal reference" definition and impedes reference collapsing.
And how would that interfere with not providing template argument manually and letting compiler to deduct type?


The problem is that not providing the template arguments will result in std::bad_cast exception, when I down cast the delegate in the resolve method to concrete type. I use the resolve method to perform execution of the delegate (I've removed that from the live example to simplify the code):

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
template <typename T, typename ... ARGS>
inline std::shared_ptr<T> brIOCContainer::resolve(ARGS&& ... args) const
{	
	std::type_index type = typeid(T);
	
	// first check for possible singleton instances
	auto siter = m_instances.find(type);
	if(siter!=m_instances.end()){
		auto component = std::dynamic_pointer_cast<Component<T>>((siter->second));
        return component->m_instance;
	}
		
	// try to resolve transient object types
	auto count = m_delegates.count(type);
	if(count == 0){
		std::string msg = "[brIOCContainer]:resolve: No entry found for requested class type: ";
		msg.append(brStringUtils::demangle(type.name()));		
		throw brRegistrationException(msg);
	}
	
	std::shared_ptr<brDelegate> delegate = nullptr;
	if(count == 1){
		auto iter = m_delegates.find(type);
		delegate = (iter->second);
	}
	else{
		auto range = m_delegates.equal_range(type);	
		for (auto iter=range.first; iter!=range.second; ++iter)
		{
			if((iter->second)->isSignatureEquals<T, ARGS...>())
			{
				delegate = (iter->second);
				break;
			}
		}	
		if(delegate==nullptr){
			std::string msg = "[brIOCContainer]:resolve: No delegate factory found for requested class type: ";
			msg.append(brStringUtils::demangle(type.name()));		
			msg.append(" with an equals signature!");
			throw brRegistrationException(msg);
		}
	}
	
	return delegate->run<T, ARGS...>(std::forward<ARGS>(args)...);	
}


Depends on code. Cannot say for sure.

When you look at the live example, where is the difference?

Edit: Update live example to simulate delegate call: http://ideone.com/mupCXp
Last edited on
> And why it is working if I use std::make shared for the pointer instead of the lambda expression?
> And why in some cases it works also with the lambda?

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
int main() {

    auto delegate1 = brDelegate::create<MachineGun, double, std::string>
	    ([] (double caliber, const std::string& name) -> std::shared_ptr<MachineGun>
		{ return std::make_shared<MachineGun>(caliber, name); });
        
    auto gun = delegate1->run<MachineGun, double, std::string>(50.0, "BAR");
    // *** type of gun is std::shared_ptr<MachineGun>
    
    // *** this will compile cleanly
    auto test = resolve<Armament, std::shared_ptr<Gun>>( gun );
    // *** equivalent to auto test = resolve<Armament, std::shared_ptr<Gun>>( std::shared_ptr<Gun>(gun) ) ; // rvalue

    auto delegate2 = brDelegate::create<brTexture, brTexture::eTextureType>
		([] (brTexture::eTextureType type) -> std::shared_ptr<brTexture>
			{ return std::make_shared<brGL3Texture>(type); });

    auto texture0 = std::make_shared<brGL3Texture>(brTexture::TYPE_TEXTURE_2D);
    // *** type of texture0 is std::shared_ptr<brGL3Texture>
    
    // *** this will compile cleanly
    auto test0 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>( texture0 ) ; // rvalue
    // *** equivalent to auto test = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>( std::shared_ptr<brTexture>(texture0) ) ; // rvalue
    
	auto texture = delegate2->run<brTexture, brTexture::eTextureType>(brTexture::TYPE_TEXTURE_2D);
    // *** type of texture is std::shared_ptr<brTexture>
    
    // auto test1 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>( texture ) ; // lvalue
    // *** error *** no conversion from std::shared_ptr<brTexture>& to std::shared_ptr<brTexture>&&
    
    // *** this will compile cleanly
    auto test2 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>( std::shared_ptr<brTexture>(texture) ); // rvalue
    
    // *** this will also compile cleanly; but donot use the object texture after this
    auto test3 = resolve<brGL3TextureSampler, std::shared_ptr<brTexture>>( std::move(texture) ); // rvalue
}

http://coliru.stacked-crooked.com/a/2713b65a719a6453
Ahh, I see, I've fall into the typically lvalue/rvalue trap! Thank you for enlighten me!

Question: Should I avoid perfect forwarding at this point? Or let it be as it is?
Haven't we discussed this earlier?

The simplest solution (one not requiring any metaprogramming) would be:

a. Avoid reference collapsing altogether by passing all parameters by value.

b. If passing by reference is required, simulate it by wrapping the parameter in std::reference_wrapper<>
http://www.cplusplus.com/forum/general/143019/#msg755280
Haven't we discussed this earlier?

Sure we have, but back than, shared_pointer references are not in game ;o)
I will keep your advises in mind. Thanks for your patience.
Last edited on
Topic archived. No new replies allowed.