Reputation: 4113
I'm trying to mimic the behavior of inheritable constructors, since they are yet to be implemented in g++.
I tried the techniques described here https://stackoverflow.com/a/5411992/234261 but I'm running into issues when I want to add my own constructors. Specifically, I am trying to add a specialized copy constructor, as well as inheriting all other constructors. Unfortunately, the inherited, more generic, copy constructor is always called instead.
#include <iostream>
struct A {
A(){}
A(const A &){ std::cout << "IN A" << std::endl; }
};
struct B : public A{
template<typename ...Args,
typename = typename std::enable_if
<
std::is_constructible<A, Args...>::value
>::type>
B(Args &&...args)
: A(std::forward<Args>(args)...) {}
// These don't work either.
//B(const A &a):A(a){ std::cout << "IN B-A" << std::endl; }
//B(const B &b):A(b){ std::cout << "IN B-B" << std::endl; }
};
template<>
B::B(const A &a):A(a){ std::cout << "IN B-A" << std::endl; }
template<>
B::B(const B &b):A(b){ std::cout << "IN B-B" << std::endl; }
int main(){
A a; // 1. Prints nothing as expected
B b(a); // 2. Prints "IN A" only
B b1(b); // 3. Prints "IN A" only
return 0;
}
I would like [3] to print IN A and then IN B-B. Somehow I have managed to get [2] to work in my actual code, but can't repeat it in this small example for some reason.
I understand that the template constructor is being created due to the fact that the subclass(B) can actually be used to construct the superclass(A).
Why, however, does my explicit specialization not get invoked? Am I specializing incorrectly? Is there a better way?
Link to this example if anyone wants to run it http://ideone.com/eUHD5
I'm using gcc 4.6
Upvotes: 3
Views: 141
Reputation: 219588
This will work without the need to cast things to const:
#include <iostream>
struct A {
A(){}
A(const A &){ std::cout << "IN A" << std::endl; }
};
struct B : public A{
B() = default;
template<typename A0, typename ...Args,
typename = typename std::enable_if
<
!std::is_same<typename std::decay<A0>::type, A>::value &&
!std::is_same<typename std::decay<A0>::type, B>::value &&
std::is_constructible<A, A0, Args...>::value
>::type>
B(A0&& a0, Args &&...args)
: A(std::forward<A0>(a0), std::forward<Args>(args)...) {}
B(const A &a):A(a){ std::cout << "IN B-A" << std::endl; }
B(const B &b):A(b){ std::cout << "IN B-B" << std::endl; }
};
int main(){
A a; // 1. Prints nothing as expected
B b(a); // 2. Prints "IN A" only
B b1(b); // 3. Prints "IN A" only
return 0;
}
Though it is messier for the author, but cleaner for the client.
Upvotes: 2
Reputation: 477710
The following "works":
struct B : public A
{
template<typename ...Args, typename = typename std::enable_if<std::is_constructible<A, Args...>::value>::type>
B(Args &&...args) : A(std::forward<Args>(args)...) {}
B(const A & a) : A(a){ std::cout << "IN B-A" << std::endl; }
B(const B & b) : A(b){ std::cout << "IN B-B" << std::endl; }
};
int main()
{
A a;
B b(a);
B b1(static_cast<B const &>(b));
}
It's the old template-overload-is-a-better-match chestnut. See STL's lecture #3 for a great in-depth explanation. Basically, binding b
to a reference deducing the template parameter Args... = { B }
matches perfectly as the identity, while binding it to the B const &
requires a qualifier conversion, which is a strict superset of the identity transformation.
With the explicit conversion to const-reference, both the templated and the non-template constructor are now perfect matches, but being a non-template is the tie breaker.
Upvotes: 4