diedthreetimes
diedthreetimes

Reputation: 4113

Specialized template of a templated constructor does not get invoked

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.

An example

#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

Answers (2)

Howard Hinnant
Howard Hinnant

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

Kerrek SB
Kerrek SB

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

Related Questions