I think that with following you can go back to version with two constructors:
class my_class
{
typedef std::shared_ptr< A > a_ptr_type;
typedef std::shared_ptr< B> b_ptr_type;
typedef std::shared_ptr< C > c_ptr_type;
a_ptr_type a_ptr_;
b_ptr_type b_ptr_;
c_ptr_type c_ptr_;
template < class T > T&& store_impl(T&& v, std::false_type const& )
{
return std::forward<T>( v );
}
template < class T > T&& store_impl(T&& v, std::true_type const& )
{
return std::move( v );
}
template < class Type >
Type&& store(Type&& v)
{
return store_impl(std::forward<Type>(v),
typename std::is_rvalue_reference<Type>::type());
}
template < class T >
using remove_const_reference =
typename std::remove_const<typename std::remove_reference< T >::type>;
public:
template < class Type, class U, typename std::enable_if< std::is_same< B,
typename remove_const_reference< U >::type >::value, int >::type = 0 >
my_class(Type&& a, U&& b) :
a_ptr_(std::make_shared< A >(std::forward< Type >(a))),
b_ptr_(std::make_shared< B >(store(std::forward< U >(b)))),
c_ptr_(nullptr)
{
}
template < class Type, class U, typename std::enable_if< std::is_same< C,
typename remove_const_reference< U >::type >::value, int >::type = 0 >
my_class(Type&& a, U&& c) :
a_ptr_(std::make_shared< A >(std::forward< Type >(a))),
b_ptr_(nullptr),
c_ptr_(std::make_shared< C >(store(std::forward< U >(c))))
{
}
};
Since you have always two input types, and only assignment of the members variable is vary two ctors should be sufficient. To determine which member should be assigned function overloading and type traits are used (you can always use template class and its specialization instead of overloading store() function - eg.
template< class T, bool is_rval > struct store;
template< class T> struct store< T, false>
{
static T&& apply(T&& ) { ... } };
template<class T> struct struct store< T, true >
{
static T&& apply(T&& ) { ... } };
).
Using SFINAE, reference collapsing rules and function overloading two constructors have been eliminated. Drawback is that we need to add store() layer which assures that proper constructors of A,B or C will be invoked (copy vs. move ctor). Hence, the code isn't much shorter.
Although, store() or similar can be moved outside the class and reused in different places. To reduce verbosity local macros can be used in some places.
Following code uses presented by me approach and it was tested using clang++3.2, g++ 4.7.2 and g++ 4.8.0. Def or undef DEF_4CTORS to switch between version with four or two ctors. Observable behaviour is the same with both versions.
#include <iostream>
#include <memory>
#include <type_traits>
#include <string>
struct A {
A(){
std::cout << "A()" << std::endl;
}
A(A const& other) : s(other.s)
{
std::cout << "A(A const& other)" << std::endl;
}
A(A&& other) : s(std::move(other.s))
{
std::cout << "A(A&& other)" << std::endl;
}
std::string s{"A"};
};
struct B {
B() {
std::cout << "B()" << std::endl;
}
B(B const& other) : s(other.s)
{
std::cout << "B(B const& other)" << std::endl;
}
B(B&& other) : s(std::move(other.s))
{
std::cout << "B(B&& other)" << std::endl;
}
std::string s{"B"};
};
struct C {
C() {
std::cout << "C()" << std::endl;
}
C(C const& other) : s(other.s)
{
std::cout << "C(C const& other)" << std::endl;
}
C(C&& other) : s(std::move(other.s))
{
std::cout << "C(C&& other)" << std::endl;
}
std::string s{"C"};
};
class my_class
{
typedef std::shared_ptr< A > a_ptr_type;
typedef std::shared_ptr< B > b_ptr_type;
typedef std::shared_ptr< C > c_ptr_type;
a_ptr_type a_ptr_;
b_ptr_type b_ptr_;
c_ptr_type c_ptr_;
public:
#ifdef DEF_4CTORS
template <typename Type>
my_class(Type&& a, const B& b) :
a_ptr_(std::make_shared<A>(std::forward<Type>(a))),
b_ptr_(std::make_shared< B>(b)),
c_ptr_(nullptr)
{
std::cout << "my_class(Type&& a, const B& b)" << std::endl;
}
template <typename Type>
my_class(Type&& a, B&& b) :
a_ptr_(std::make_shared<A>(std::forward<Type>(a))),
b_ptr_(std::make_shared< B>(std::move(b))),
c_ptr_(nullptr)
{
std::cout << "my_class(Type&& a, B&& b)" << std::endl;
}
template <typename Type>
my_class(Type&& a, const C& c) :
a_ptr_(std::make_shared<A>(std::forward<Type>(a))),
b_ptr_(nullptr),
c_ptr_(std::make_shared<C>(c))
{
std::cout << "my_class(Type&& a, const C& c)" << std::endl;
}
template <typename Type>
my_class(Type&& a, C&& c) :
a_ptr_(std::make_shared<A>(std::forward<Type>(a))),
b_ptr_(nullptr),
c_ptr_(std::make_shared<C>(std::move(c)))
{
std::cout << "my_class(Type&& a, C&& c)" << std::endl;
}
#else
private:
template<class T> T&& store_impl(T&& v, std::false_type const& )
{
return std::forward<T>( v );
}
template<class T> T&& store_impl(T&& v, std::true_type const& )
{
return std::move( v );
}
template<class Type>
Type&& store(Type&& v)
{
return store_impl(std::forward<Type>(v),
typename std::is_rvalue_reference<Type>::type());
}
template < class T >
using remove_const_reference =
typename std::remove_const<typename std::remove_reference< T >::type>;
public:
template < class Type, class U, typename std::enable_if< std::is_same< B,
typename remove_const_reference< U >::type >::value, int >::type = 0 >
my_class(Type&& a, U&& b) :
a_ptr_(std::make_shared< A >(std::forward< Type >(a))),
b_ptr_(std::make_shared< B >(store(std::forward< U >(b)))),
c_ptr_(nullptr)
{
std::cout << "my_class(Type&& a, U&& b)" << std::endl;
}
template < class Type, class U, typename std::enable_if< std::is_same< C,
typename remove_const_reference< U >::type >::value, int >::type = 0 >
my_class(Type&& a, U&& c) :
a_ptr_(std::make_shared< A >(std::forward< Type >(a))),
b_ptr_(nullptr),
c_ptr_(std::make_shared< C >(store(std::forward< U >(c))))
{
std::cout << "my_class(Type&& a, U&& c)" << std::endl;
}
#endif
};
int main()
{
my_class c1( (A()), (B()) );
my_class c2( (A()), (C()) );
A a;
const B b;
C c;
my_class c3(a, b);
my_class c4(a, c);
}
I hope it can help you and using my insights you'll find suitable solution for you.
Regards!